From 305daab9aa65f595a1740203bad6459e1162aee9 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Fri, 12 Jan 2018 16:11:48 +0000 Subject: [PATCH 01/47] Add first version of getdns_server_mon. Currently only QNAME minimisation check is working. --- Makefile.in | 9 +- configure.ac | 16 + src/Makefile.in | 3 + src/tools/Makefile.in | 21 +- src/tools/getdns_server_mon.c | 647 ++++++++++++++++++++++++++++++++++ 5 files changed, 691 insertions(+), 5 deletions(-) create mode 100644 src/tools/getdns_server_mon.c diff --git a/Makefile.in b/Makefile.in index 63216493..d126a0ba 100644 --- a/Makefile.in +++ b/Makefile.in @@ -44,7 +44,7 @@ libdir = @libdir@ srcdir = @srcdir@ INSTALL = @INSTALL@ -all : default @GETDNS_QUERY@ +all : default @GETDNS_QUERY@ @GETDNS_SERVER_MON@ everything: default cd src/test && $(MAKE) @@ -55,7 +55,7 @@ default: install-lib: cd src && $(MAKE) install -install: getdns.pc getdns_ext_event.pc install-lib @INSTALL_GETDNS_QUERY@ +install: getdns.pc getdns_ext_event.pc install-lib @INSTALL_GETDNS_QUERY@ @INSTALL_GETDNS_SERVER_MON@ $(INSTALL) -m 755 -d $(DESTDIR)$(docdir) $(INSTALL) -m 644 $(srcdir)/AUTHORS $(DESTDIR)$(docdir) $(INSTALL) -m 644 $(srcdir)/ChangeLog $(DESTDIR)$(docdir) @@ -93,7 +93,7 @@ install: getdns.pc getdns_ext_event.pc install-lib @INSTALL_GETDNS_QUERY@ @echo "*** trust anchor management keeping it up-to-date." @echo "***" -uninstall: @UNINSTALL_GETDNS_QUERY@ +uninstall: @UNINSTALL_GETDNS_QUERY@ @UNINSTALL_GETDNS_SERVER_MON@ rm -rf $(DESTDIR)$(docdir) cd doc && $(MAKE) $@ cd src && $(MAKE) $@ @@ -110,6 +110,9 @@ test: default getdns_query: default cd src/tools && $(MAKE) $@ +getdns_server_mon: default + cd src/tools && $(MAKE) $@ + stubby: cd src && $(MAKE) $@ diff --git a/configure.ac b/configure.ac index 9bc7f66a..eb05012b 100644 --- a/configure.ac +++ b/configure.ac @@ -1150,6 +1150,22 @@ AC_SUBST(GETDNS_QUERY) AC_SUBST(INSTALL_GETDNS_QUERY) AC_SUBST(UNINSTALL_GETDNS_QUERY) +AC_ARG_WITH(getdns_server_mon, AS_HELP_STRING([--without-getdns_server_mon], + [Do not compile and install the getdns_server_mon tool]), + [], [withval="yes"]) +if test x_$withval = x_no; then + GETDNS_SERVER_MON="" + INSTALL_GETDNS_SERVER_MON="" + UNINSTALL_GETDNS_SERVER_MON="" +else + GETDNS_SERVER_MON="getdns_server_mon" + INSTALL_GETDNS_SERVER_MON="install-getdns_server_mon" + UNINSTALL_GETDNS_SERVER_MON="uninstall-getdns_server_mon" +fi +AC_SUBST(GETDNS_SERVER_MON) +AC_SUBST(INSTALL_GETDNS_SERVER_MON) +AC_SUBST(UNINSTALL_GETDNS_SERVER_MON) + stubby_with_yaml=0 AC_ARG_WITH(stubby, AS_HELP_STRING([--with-stubby], [Compile and install stubby, the (stub) resolver daemon]), diff --git a/src/Makefile.in b/src/Makefile.in index b37d296e..361205b5 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -197,6 +197,9 @@ test: default getdns_query: default cd tools && $(MAKE) $@ +getdns_server_mon: default + cd tools && $(MAKE) $@ + stubby.lo: $(stubbysrcdir)/src/stubby.c $(LIBTOOL) --quiet --tag=CC --mode=compile $(CC) $(CFLAGS) $(WPEDANTICFLAG) -DSTUBBYCONFDIR=\"$(sysconfdir)/stubby\" -DRUNSTATEDIR=\"$(runstatedir)\" -c $< -o $@ diff --git a/src/tools/Makefile.in b/src/tools/Makefile.in index 3cba9659..88d5f21d 100644 --- a/src/tools/Makefile.in +++ b/src/tools/Makefile.in @@ -45,9 +45,9 @@ CFLAGS=-I$(srcdir)/.. -I$(srcdir) -I.. $(cflags) @CFLAGS@ @CPPFLAGS@ $(WPEDANTIC LDFLAGS=-L.. @LDFLAGS@ LDLIBS=../libgetdns.la @LIBS@ -ALL_OBJS=getdns_query.lo +ALL_OBJS=getdns_query.lo getdns_server_mon.lo -PROGRAMS=getdns_query +PROGRAMS=getdns_query getdns_server_mon .SUFFIXES: .c .o .a .lo .h @@ -68,6 +68,9 @@ $(ALL_OBJS): getdns_query: getdns_query.lo $(LIBTOOL) --tag=CC --mode=link $(CC) $(CFLAGS) -o $@ getdns_query.lo $(LDFLAGS) $(LDLIBS) +getdns_server_mon: getdns_server_mon.lo + $(LIBTOOL) --tag=CC --mode=link $(CC) $(CFLAGS) -o $@ getdns_server_mon.lo $(LDFLAGS) $(LDLIBS) + stubby: cd .. && $(MAKE) $@ @@ -78,6 +81,13 @@ install-getdns_query: getdns_query uninstall-getdns_query: $(LIBTOOL) --mode=uninstall rm -f $(DESTDIR)$(bindir)/getdns_query +install-getdns_server_mon: getdns_server_mon + $(INSTALL) -m 755 -d $(DESTDIR)$(bindir) + $(LIBTOOL) --mode=install cp getdns_server_mon $(DESTDIR)$(bindir) + +uninstall-getdns_server_mon: + $(LIBTOOL) --mode=uninstall rm -f $(DESTDIR)$(bindir)/getdns_server_mon + install-stubby: cd .. && $(MAKE) $@ @@ -117,3 +127,10 @@ getdns_query.lo getdns_query.o: $(srcdir)/getdns_query.c \ $(srcdir)/../debug.h \ ../getdns/getdns.h \ ../getdns/getdns_extra.h + +# Dependencies for getdns_server_mon +getdns_server_mon.lo getdns_server_mon.o: $(srcdir)/getdns_server_mon.c \ + ../config.h \ + $(srcdir)/../debug.h \ + ../getdns/getdns.h \ + ../getdns/getdns_extra.h diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c new file mode 100644 index 00000000..34c6e3c2 --- /dev/null +++ b/src/tools/getdns_server_mon.c @@ -0,0 +1,647 @@ +/* + * Copyright (c) 2018, NLNet Labs, Sinodun + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the names of the copyright holders nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "debug.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#define APP_NAME "getdns_server_mon" + +#define EXAMPLE_PIN "pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\"" + +/* Plugin exit values */ +typedef enum { + EXIT_OK = 0, + EXIT_WARNING, + EXIT_CRITICAL, + EXIT_UNKNOWN +} exit_value_t; + +/* Plugin verbosity values */ +typedef enum { + VERBOSITY_MINIMAL = 0, + VERBOSITY_ADDITIONAL, + VERBOSITY_CONFIG, + VERBOSITY_DEBUG +} verbosity_t; + +static struct test_info_s +{ + getdns_context *context; + + /* Output control */ + bool monitoring; + FILE *errout; + verbosity_t verbosity; + + /* Test config info */ + bool fail_on_dns_errors; +} test_info; + +static const char *rcode_text(int rcode) +{ + const char *text[] = { + "OK", + "FORMERR", + "SERVFAIL", + "NXDOMAIN", + "NOTIMP", + "REFUSED" + }; + + if ((size_t) rcode >= sizeof(text) / sizeof(text[0])) + return "(?)"; + else + return text[rcode]; +} + +/* Thanks to: + * https://zakird.com/2013/10/13/certificate-parsing-with-openssl + */ +static bool extract_cert_expiry(const unsigned char *data, size_t len, time_t *t) +{ + X509 *cert = d2i_X509(NULL, &data, len); + if (!cert) + return false; + + ASN1_TIME *not_after = X509_get_notAfter(cert); + + /* + * Use ASN1_TIME_diff to get a time delta between now and expiry. + * This is much easier than trying to parse the time. + */ + int day, sec; + *t = time(NULL); + ASN1_TIME_diff(&day, &sec, NULL, not_after); + *t += day * 86400 + sec; + + X509_free(cert); + return true; +} + +static void exit_tidy() +{ + if (test_info.context) + getdns_context_destroy(test_info.context); +} + +static void usage() +{ + fputs( +"Usage: " APP_NAME " [-MEr] @upstream testname [] []\n" +" -M|--monitoring Make output suitable for monitoring tools\n" +" -E|--fail-on-dns-errors Fail on DNS error (NXDOMAIN, SERVFAIL)\n" +" -T|--tls Use TLS transport\n" +" -S|--strict-usage-profile Use strict profile (require authentication)\n" +" -K|--spki-pin SPKI pin for TLS connections (can repeat)\n" +" -v|--verbose Increase output verbosity\n" +" -V|--version Report GetDNS version\n" +"\n" +"spki-pin: Should look like '" EXAMPLE_PIN "'\n" +"\n" +"upstream: @[%][#][~tls name>][^]\n" +" @ may be given as : or\n" +" '['[%]']':\n" +"\n" +"tsig spec: [:]:\n" +"\n" +"Tests:\n" +" auth [ []] Check authentication of TLS server\n" +" If both a SPKI pin and authentication name are\n" +" provided, both must authenticate for this test\n" +" to pass.\n" +" qname-min Check whether server supports QNAME minimisation\n" +" cert-valid [ [type]] [warn-days,crit-days]\n" +" Check server certificate validity, report\n" +" warning or critical if days to expiry at\n" +" or below thresholds (default 14,7).\n" +"\n" +"Enabling monitoring mode ensures output messages and exit statuses conform\n" +"to the requirements of monitoring plugins (www.monitoring-plugins.org).\n", + test_info.errout); + exit(EXIT_UNKNOWN); +} + +static void version() +{ + fputs(APP_NAME ": getdns " GETDNS_VERSION " , API " GETDNS_API_VERSION ".\n", + test_info.errout); + exit(EXIT_UNKNOWN); +} + +static exit_value_t search(const struct test_info_s *test_info, + const char *name, + uint16_t type, + getdns_dict **response) +{ + getdns_return_t ret; + getdns_dict *extensions = getdns_dict_create(); + + if ((ret = getdns_dict_set_int(extensions, "return_call_reporting", GETDNS_EXTENSION_TRUE)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot set return call reporting: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + getdns_dict_destroy(extensions); + return EXIT_UNKNOWN; + } + if ((ret = getdns_dict_set_int(extensions, "return_both_v4_and_v6", GETDNS_EXTENSION_TRUE)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot set return both IPv4 and IPv6: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + getdns_dict_destroy(extensions); + return EXIT_UNKNOWN; + } + + if (test_info->verbosity >= VERBOSITY_DEBUG) { + fprintf(test_info->errout, + "Context: %s\n", + getdns_pretty_print_dict(getdns_context_get_api_information(test_info->context))); + } + + ret = getdns_general_sync(test_info->context, + name, + type, + extensions, + response); + getdns_dict_destroy(extensions); + if (ret != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Error resolving '%s': %s (%d)", + name, + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_CRITICAL; + } + + if (test_info->verbosity >= VERBOSITY_DEBUG) { + fprintf(test_info->errout, + "Response: %s\n", + getdns_pretty_print_dict(*response)); + } + + return EXIT_OK; +} + +static exit_value_t check_result(const struct test_info_s *test_info, + const getdns_dict *response) +{ + getdns_return_t ret; + uint32_t error_id; + + if ((ret = getdns_dict_get_int(response, "status", &error_id)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get result status: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + if (test_info->verbosity >= VERBOSITY_ADDITIONAL){ + fprintf(test_info->errout, + "result: %s (%d), ", + getdns_get_errorstr_by_id(error_id), + error_id); + } + + if (error_id == GETDNS_RESPSTATUS_GOOD) + return EXIT_OK; + + uint32_t rcode; + + ret = getdns_dict_get_int(response, "/replies_tree/0/header/rcode", &rcode); + if (ret == GETDNS_RETURN_NO_SUCH_DICT_NAME || + ret == GETDNS_RETURN_NO_SUCH_LIST_ITEM) { + fputs("Search had no results, timeout?", test_info->errout); + return EXIT_CRITICAL; + } else if (ret != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get DNS return code: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + if (test_info->fail_on_dns_errors && rcode > 0) { + fprintf(test_info->errout, + "DNS error %s (%d)", + rcode_text(rcode), + rcode); + return EXIT_CRITICAL; + } + + return EXIT_OK; +} + +static exit_value_t get_report_info(const struct test_info_s *test_info, + const getdns_dict *response, + uint32_t *rtt, + getdns_bindata **auth_status, + time_t *cert_expire_time) +{ + getdns_return_t ret; + getdns_list *l; + uint32_t rtt_val; + getdns_bindata *auth_status_val = NULL; + time_t cert_expire_time_val = 0; + + 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; + } + if ((ret = getdns_dict_get_int(d, "run_time/ms", &rtt_val)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get RTT: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + if (rtt) + *rtt = rtt_val; + if (test_info->verbosity >= VERBOSITY_ADDITIONAL) + fprintf(test_info->errout, "RTT %dms, ", rtt_val); + + if (getdns_dict_get_bindata(d, "tls_auth_status", &auth_status_val) == GETDNS_RETURN_GOOD) { + /* Just in case - not sure this is necessary */ + auth_status_val->data[auth_status_val->size] = '\0'; + if (test_info->verbosity >= VERBOSITY_ADDITIONAL) + fprintf(test_info->errout, "auth. %s, ", (char *) auth_status_val->data); + } + if (auth_status) + *auth_status = auth_status_val; + + getdns_bindata *cert; + if (getdns_dict_get_bindata(d, "tls_peer_cert", &cert) == GETDNS_RETURN_GOOD) { + if (!extract_cert_expiry(cert->data, cert->size, &cert_expire_time_val)) { + fputs("Cannot parse PKIX certificate", test_info->errout); + return EXIT_UNKNOWN; + } + if (test_info->verbosity >= VERBOSITY_ADDITIONAL) { + struct tm *tm = gmtime(&cert_expire_time_val); + char buf[25]; + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm); + fprintf(test_info->errout, "cert expiry %s, ", buf); + } + } + if (cert_expire_time) + *cert_expire_time = cert_expire_time_val; + + return EXIT_OK; +} + +/** + * Test routines. + */ + +static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, + char ** av) +{ + if (*av) { + fputs("qname-minimisation takes no arguments", + test_info->errout); + return EXIT_UNKNOWN; + } + + getdns_dict *response; + exit_value_t xit = search(test_info, "qnamemintest.internet.nl", GETDNS_RRTYPE_TXT, &response); + if (xit != EXIT_OK) + return xit; + + xit = check_result(test_info, response); + if (xit != EXIT_OK) + return xit; + + /* Don't need any of this, but do want check and verbosity reporting. */ + xit = get_report_info(test_info, response, NULL, NULL, NULL); + if (xit != EXIT_OK) + return xit; + + getdns_list *answers; + getdns_return_t ret; + + if ((ret = getdns_dict_get_list(response, "/replies_tree/0/answer", &answers)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get answers: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + size_t no_answers; + if ((ret = getdns_list_get_length(answers, &no_answers)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get number of answers: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + if (no_answers <= 0) { + fputs("Got zero answers", test_info->errout); + return EXIT_WARNING; + } + + for (size_t i = 0; i < no_answers; ++i) { + getdns_dict *answer; + + if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get answer number %zu: %s (%d)", + i, + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + uint32_t rtype; + + if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get answer type: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + if (rtype != GETDNS_RRTYPE_TXT) + continue; + + getdns_bindata *rtxt; + if ((ret = getdns_dict_get_bindata(answer, "/rdata/txt_strings/0", &rtxt)) != GETDNS_RETURN_GOOD) { + fputs("No answer text", test_info->errout); + return EXIT_WARNING; + } + + if (rtxt->size > 0 ) { + switch(rtxt->data[0]) { + case 'H': + fputs("QNAME minimisation ON", test_info->errout); + return EXIT_OK; + + case 'N': + fputs("QNAME minimisation OFF", test_info->errout); + return EXIT_WARNING; + + default: + /* Unrecognised message. */ + break; + } + } + } + + fputs("No valid QNAME minimisation data", test_info->errout); + return EXIT_UNKNOWN; +} + +static struct test_funcs_s +{ + const char *name; + exit_value_t (*func)(const struct test_info_s *test_info, char **av); +} TESTS[] = +{ + { "qname-min", test_qname_minimisation }, + { NULL, NULL } +}; + +int main(int ATTR_UNUSED(ac), char *av[]) +{ + getdns_return_t ret; + getdns_list *pinset = NULL; + size_t pinset_size = 0; + bool strict_usage_profile = false; + bool use_tls = false; + + test_info.errout = stderr; + atexit(exit_tidy); + if ((ret = getdns_context_create(&test_info.context, 1)) != GETDNS_RETURN_GOOD) { + fprintf(test_info.errout, + "Create context failed: %s (%d)\n", + getdns_get_errorstr_by_id(ret), + ret); + exit(EXIT_UNKNOWN); + } + + for (++av; *av && *av[0] == '-'; ++av) { + if (strcmp(*av, "-M") == 0 || + strcmp(*av, "--monitoring") == 0) { + test_info.monitoring = true; + test_info.errout = stdout; + } else if (strcmp(*av, "-E") == 0 || + strcmp(*av, "--fail-on-dns-errors") == 0) { + test_info.fail_on_dns_errors = true; + } else if (strcmp(*av, "-T") == 0 || + strcmp(*av, "--tls") == 0 ) { + use_tls = true; + } else if (strcmp(*av, "-S") == 0 || + strcmp(*av, "--strict-usage-profile") == 0 ) { + strict_usage_profile = true; + use_tls = true; + } else if (strcmp(*av, "-K") == 0 || + strcmp(*av, "--spki-pin") == 0 ) { + ++av; + if (!*av) { + fputs("pin string of the form " EXAMPLE_PIN "expected after -K|--pin\n", test_info.errout); + exit(EXIT_UNKNOWN); + } + + getdns_dict *pin; + + pin = getdns_pubkey_pin_create_from_string(test_info.context, *av); + if (!pin) { + fprintf(test_info.errout, + "Could not convert '%s' into a public key pin.\n" + "Good pins look like: " EXAMPLE_PIN "\n" + "Please see RFC 7469 for details about the format.\n", *av); + exit(EXIT_UNKNOWN); + } + if (!pinset) + pinset = getdns_list_create_with_context(test_info.context); + ret = getdns_list_set_dict(pinset, + pinset_size++, + pin); + getdns_dict_destroy(pin); + if (ret != GETDNS_RETURN_GOOD) { + fprintf(test_info.errout, + "Could not add pin '%s' to pin set.\n", + *av); + getdns_list_destroy(pinset); + exit(EXIT_UNKNOWN); + + } + use_tls = true; + } else if (strcmp(*av, "-v") == 0 || + strcmp(*av, "--verbose") == 0) { + ++test_info.verbosity; + } else if (strcmp(*av, "-V") == 0 || + strcmp(*av, "--version") == 0) { + version(); + } else { + usage(); + } + } + + if (*av == NULL || *av[0] != '@') + usage(); + + const char *upstream = *av++; + getdns_dict *resolver; + getdns_bindata *address; + + if ((ret = getdns_str2dict(&upstream[1], &resolver)) != GETDNS_RETURN_GOOD) { + fprintf(test_info.errout, + "Could not convert \"%s\" to an IP dict: %s (%d)\n", + &upstream[1], + getdns_get_errorstr_by_id(ret), + ret); + exit(EXIT_UNKNOWN); + } + if ((ret = getdns_dict_get_bindata(resolver, "address_data", &address)) != GETDNS_RETURN_GOOD) { + fprintf(test_info.errout, + "\"%s\" did not translate to an IP dict: %s (%d)\n", + &upstream[1], + getdns_get_errorstr_by_id(ret), + ret); + getdns_dict_destroy(resolver); + exit(EXIT_UNKNOWN); + } + + /* Set parameters on the resolver. */ + if (pinset) { + ret = getdns_dict_set_list(resolver, + "tls_pubkey_pinset", + pinset); + getdns_list_destroy(pinset); + if (ret != GETDNS_RETURN_GOOD) { + fprintf(test_info.errout, + "Cannot set keys for \"%s\": %s (%d)\n", + &upstream[1], + getdns_get_errorstr_by_id(ret), + ret); + exit(EXIT_UNKNOWN); + } + } + + /* Set getdns context to use the indicated resolver. */ + getdns_list *l = getdns_list_create(); + ret = getdns_list_set_dict(l, 0, resolver); + getdns_dict_destroy(resolver); + if (ret != GETDNS_RETURN_GOOD) { + fprintf(test_info.errout, + "Unable to add upstream '%s' to list: %s (%d)\n", + upstream, + getdns_get_errorstr_by_id(ret), + ret); + getdns_list_destroy(l); + exit(EXIT_UNKNOWN); + } + ret = getdns_context_set_upstream_recursive_servers(test_info.context, l); + getdns_list_destroy(l); + if (ret != GETDNS_RETURN_GOOD) { + fprintf(test_info.errout, + "Unable to set upstream resolver to '%s': %s (%d)\n", + upstream, + getdns_get_errorstr_by_id(ret), + ret); + exit(EXIT_UNKNOWN); + } + + /* Set context to stub mode. */ + if ((ret = getdns_context_set_resolution_type(test_info.context, GETDNS_RESOLUTION_STUB)) != GETDNS_RETURN_GOOD) { + fprintf(test_info.errout, + "Unable to set stub mode: %s (%d)\n", + getdns_get_errorstr_by_id(ret), + ret); + exit(EXIT_UNKNOWN); + } + + /* Set other context parameters. */ + if (use_tls) { + getdns_transport_list_t t[] = { GETDNS_TRANSPORT_TLS }; + if ((ret = getdns_context_set_dns_transport_list(test_info.context, 1, t)) != GETDNS_RETURN_GOOD) { + fprintf(test_info.errout, + "Unable to set TLS transport: %s (%d)\n", + getdns_get_errorstr_by_id(ret), + ret); + exit(EXIT_UNKNOWN); + } + } + + if (strict_usage_profile) { + ret = getdns_context_set_tls_authentication(test_info.context, GETDNS_AUTHENTICATION_REQUIRED); + if (ret != GETDNS_RETURN_GOOD) { + fprintf(test_info.errout, + "Unable to set strict profile: %s (%d)\n", + getdns_get_errorstr_by_id(ret), + ret); + exit(EXIT_UNKNOWN); + } + } + + /* Choose and run test */ + const char *testname = *av; + + if (!testname) + usage(); + ++av; + + for (const struct test_funcs_s *f = TESTS; + f->name != NULL; + ++f) { + if (strcmp(testname, f->name) == 0) { + exit_value_t xit = f->func(&test_info, av); + fputc('\n', test_info.errout); + exit(xit); + } + } + fprintf(test_info.errout, "Unknown test %s\n", testname); + exit(EXIT_UNKNOWN); +} From e597daa4c0986ea012a16e4f4ddabf435eaf51b2 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Fri, 12 Jan 2018 17:21:00 +0000 Subject: [PATCH 02/47] Add 'auth' test. --- src/tools/getdns_server_mon.c | 201 ++++++++++++++++++++++++++++++---- 1 file changed, 177 insertions(+), 24 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 34c6e3c2..21354ad1 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -28,6 +28,7 @@ #include "config.h" #include "debug.h" +#include #include #include #include @@ -76,6 +77,31 @@ static struct test_info_s bool fail_on_dns_errors; } test_info; +static int get_rrtype(const char *t) +{ + char buf[1024] = "GETDNS_RRTYPE_"; + uint32_t rrtype; + long int l; + size_t i; + char *endptr; + + if (strlen(t) > sizeof(buf) - 15) + return -1; + for (i = 14; *t && i < sizeof(buf) - 1; i++, t++) + buf[i] = *t == '-' ? '_' : toupper(*t); + buf[i] = '\0'; + + if (!getdns_str2int(buf, &rrtype)) + return (int)rrtype; + + if (strncasecmp(buf + 14, "TYPE", 4) == 0) { + l = strtol(buf + 18, &endptr, 10); + if (!*endptr && l >= 0 && l < 65536) + return l; + } + return -1; +} + static const char *rcode_text(int rcode) { const char *text[] = { @@ -167,6 +193,39 @@ static void version() exit(EXIT_UNKNOWN); } +/** + ** Functions used by tests. + **/ + +static exit_value_t get_name_type_args(const struct test_info_s *test_info, + char ***av, + const char **lookup_name, + uint32_t *lookup_type) +{ + *lookup_name = "getdnsapi.net"; + *lookup_type = GETDNS_RRTYPE_AAAA; + + if (**av) { + if (strlen(**av) > 0) { + *lookup_name = **av; + } else { + fputs("Empty name not valid", test_info->errout); + return EXIT_UNKNOWN; + } + ++*av; + + if (**av) { + int rrtype = get_rrtype(**av); + if (rrtype >= 0) { + *lookup_type = (uint32_t) rrtype; + ++*av; + } + } + } + + return EXIT_OK; +} + static exit_value_t search(const struct test_info_s *test_info, const char *name, uint16_t type, @@ -340,15 +399,125 @@ static exit_value_t get_report_info(const struct test_info_s *test_info, return EXIT_OK; } +static exit_value_t get_answers(const struct test_info_s *test_info, + const getdns_dict *response, + getdns_list **answers, + size_t *no_answers) +{ + getdns_return_t ret; + + if ((ret = getdns_dict_get_list(response, "/replies_tree/0/answer", answers)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get answers: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + if ((ret = getdns_list_get_length(*answers, no_answers)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get number of answers: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + if (*no_answers <= 0) { + fputs("Got zero answers", test_info->errout); + return EXIT_WARNING; + } + + return EXIT_OK; +} + +static exit_value_t check_answer_type(const struct test_info_s *test_info, + const getdns_dict *response, + uint32_t rrtype) +{ + getdns_list *answers; + size_t no_answers; + exit_value_t xit; + + if ((xit = get_answers(test_info, response, &answers, &no_answers)) != EXIT_OK) + return xit; + + for (size_t i = 0; i < no_answers; ++i) { + getdns_dict *answer; + getdns_return_t ret; + + if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get answer number %zu: %s (%d)", + i, + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + uint32_t rtype; + + if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get answer type: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + if (rtype == rrtype) + return EXIT_OK; + } + + fputs("Answer does not contain expected type", test_info->errout); + return EXIT_UNKNOWN; +} + /** - * Test routines. - */ + ** Test routines. + **/ + +static exit_value_t test_authenticate(const struct test_info_s *test_info, + char ** av) +{ + const char *lookup_name; + uint32_t lookup_type; + exit_value_t xit; + + if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) + return xit; + + if (*av) { + fputs("auth takes arguments [ []]", + test_info->errout); + return EXIT_UNKNOWN; + } + + getdns_dict *response; + if ((xit = search(test_info, lookup_name, lookup_type, &response)) != EXIT_OK) + return xit; + + if ((xit = check_result(test_info, response)) != EXIT_OK) + return xit; + + getdns_bindata *auth_status; + if ((xit = get_report_info(test_info, response, NULL, &auth_status, NULL)) != EXIT_OK) + return xit; + + if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) + return xit; + + if (!auth_status || strcmp((char *) auth_status->data, "Success") != 0) { + fputs("Authentication failed", test_info->errout); + return EXIT_CRITICAL; + } else { + fputs("Authentication succeeded", test_info->errout); + return EXIT_OK; + } +} static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, char ** av) { if (*av) { - fputs("qname-minimisation takes no arguments", + fputs("qname-min takes no arguments", test_info->errout); return EXIT_UNKNOWN; } @@ -368,31 +537,14 @@ static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, return xit; getdns_list *answers; - getdns_return_t ret; - - if ((ret = getdns_dict_get_list(response, "/replies_tree/0/answer", &answers)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get answers: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); - return EXIT_UNKNOWN; - } - size_t no_answers; - if ((ret = getdns_list_get_length(answers, &no_answers)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get number of answers: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); - return EXIT_UNKNOWN; - } - if (no_answers <= 0) { - fputs("Got zero answers", test_info->errout); - return EXIT_WARNING; - } + + if ((xit = get_answers(test_info, response, &answers, &no_answers)) != EXIT_OK) + return xit; for (size_t i = 0; i < no_answers; ++i) { getdns_dict *answer; + getdns_return_t ret; if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) { fprintf(test_info->errout, @@ -448,6 +600,7 @@ static struct test_funcs_s exit_value_t (*func)(const struct test_info_s *test_info, char **av); } TESTS[] = { + { "auth", test_authenticate }, { "qname-min", test_qname_minimisation }, { NULL, NULL } }; From e7618321ce3f2952abdbb7080b7e7db9ae16c3fa Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Fri, 12 Jan 2018 18:21:38 +0000 Subject: [PATCH 03/47] Add cert-valid test. --- src/tools/getdns_server_mon.c | 125 ++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 5 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 21354ad1..8a235deb 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -46,6 +46,12 @@ #define APP_NAME "getdns_server_mon" +#define CERT_EXPIRY_CRITICAL_DAYS 7 +#define CERT_EXPIRY_WARNING_DAYS 14 + +#define DEFAULT_LOOKUP_NAME "getdnsapi.net" +#define DEFAULT_LOOKUP_TYPE GETDNS_RRTYPE_AAAA + #define EXAMPLE_PIN "pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\"" /* Plugin exit values */ @@ -197,14 +203,52 @@ static void version() ** Functions used by tests. **/ +static exit_value_t get_cert_valid_thresholds(char ***av, + int *critical_days, + int *warning_days) +{ + *critical_days = CERT_EXPIRY_CRITICAL_DAYS; + *warning_days = CERT_EXPIRY_WARNING_DAYS; + + if (**av) { + char *comma = strchr(**av, ','); + if (!comma) + return EXIT_UNKNOWN; + + char *end; + long w,c; + + c = strtol(**av, &end, 10); + /* + * If the number doesn't end at a comma, this isn't a + * properly formatted thresholds arg. Pass over it. + */ + if (end != comma) + return EXIT_UNKNOWN; + + /* + * Similarly, if the number doesn't end at the end of the + * argument, this isn't a properly formatted arg. + */ + w = strtol(comma + 1, &end, 10); + if (*end != '\0') + return EXIT_UNKNOWN; + + /* Got two numbers, so consume the argument. */ + *critical_days = (int) c; + *warning_days = (int) w; + ++*av; + return EXIT_OK; + } + + return EXIT_UNKNOWN; +} + static exit_value_t get_name_type_args(const struct test_info_s *test_info, char ***av, const char **lookup_name, uint32_t *lookup_type) { - *lookup_name = "getdnsapi.net"; - *lookup_type = GETDNS_RRTYPE_AAAA; - if (**av) { if (strlen(**av) > 0) { *lookup_name = **av; @@ -477,8 +521,8 @@ static exit_value_t check_answer_type(const struct test_info_s *test_info, static exit_value_t test_authenticate(const struct test_info_s *test_info, char ** av) { - const char *lookup_name; - uint32_t lookup_type; + const char *lookup_name = DEFAULT_LOOKUP_NAME; + uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; exit_value_t xit; if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) @@ -513,6 +557,76 @@ static exit_value_t test_authenticate(const struct test_info_s *test_info, } } +static exit_value_t test_certificate_valid(const struct test_info_s *test_info, + char **av) +{ + const char *lookup_name = DEFAULT_LOOKUP_NAME; + uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; + exit_value_t xit; + int warning_days; + int critical_days; + + /* Is first arg the threshold? */ + if (get_cert_valid_thresholds(&av, &critical_days, &warning_days) != EXIT_OK) { + if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) + return xit; + + if (*av) + get_cert_valid_thresholds(&av, &critical_days, &warning_days); + } + + if (*av) { + fputs("cert-valid takes arguments [ []] [warn-days,crit-days]", + test_info->errout); + return EXIT_UNKNOWN; + } + + getdns_dict *response; + if ((xit = search(test_info, lookup_name, lookup_type, &response)) != EXIT_OK) + return xit; + + if ((xit = check_result(test_info, response)) != EXIT_OK) + return xit; + + time_t expire_time; + if ((xit = get_report_info(test_info, response, NULL, NULL, &expire_time)) != EXIT_OK) + return xit; + + if (expire_time == 0) { + fputs("No PKIX certificate", test_info->errout); + return EXIT_CRITICAL; + } + + if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) + return xit; + + time_t now = time(NULL); + int days_to_expiry = (expire_time - now) / 86400; + + if (days_to_expiry < 0) { + fprintf(test_info->errout, + "Certificate expired %d day%s ago", + -days_to_expiry, + (days_to_expiry < -1) ? "s" : ""); + return EXIT_CRITICAL; + } + if (days_to_expiry == 0) { + fputs("Certificate expires today", test_info->errout); + return EXIT_CRITICAL; + } + fprintf(test_info->errout, + "Certificate will expire in %d day%s", + days_to_expiry, + (days_to_expiry > 1) ? "s" : ""); + if (days_to_expiry <= critical_days) { + return EXIT_CRITICAL; + } + if (days_to_expiry <= warning_days) { + return EXIT_WARNING; + } + return EXIT_OK; +} + static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, char ** av) { @@ -601,6 +715,7 @@ static struct test_funcs_s } TESTS[] = { { "auth", test_authenticate }, + { "cert-valid", test_certificate_valid }, { "qname-min", test_qname_minimisation }, { NULL, NULL } }; From 60118e9241ad26747dc6d74c33f695189b1c5501 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Sat, 13 Jan 2018 14:56:55 +0000 Subject: [PATCH 04/47] Improve cert-valid argument order to most likely first. --- src/tools/getdns_server_mon.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 8a235deb..a2d5bd5e 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -181,7 +181,7 @@ static void usage() " provided, both must authenticate for this test\n" " to pass.\n" " qname-min Check whether server supports QNAME minimisation\n" -" cert-valid [ [type]] [warn-days,crit-days]\n" +" cert-valid [warn-days,crit-days] [ [type]]\n" " Check server certificate validity, report\n" " warning or critical if days to expiry at\n" " or below thresholds (default 14,7).\n" @@ -203,9 +203,9 @@ static void version() ** Functions used by tests. **/ -static exit_value_t get_cert_valid_thresholds(char ***av, - int *critical_days, - int *warning_days) +static void get_cert_valid_thresholds(char ***av, + int *critical_days, + int *warning_days) { *critical_days = CERT_EXPIRY_CRITICAL_DAYS; *warning_days = CERT_EXPIRY_WARNING_DAYS; @@ -213,7 +213,7 @@ static exit_value_t get_cert_valid_thresholds(char ***av, if (**av) { char *comma = strchr(**av, ','); if (!comma) - return EXIT_UNKNOWN; + return; char *end; long w,c; @@ -224,7 +224,7 @@ static exit_value_t get_cert_valid_thresholds(char ***av, * properly formatted thresholds arg. Pass over it. */ if (end != comma) - return EXIT_UNKNOWN; + return; /* * Similarly, if the number doesn't end at the end of the @@ -232,16 +232,16 @@ static exit_value_t get_cert_valid_thresholds(char ***av, */ w = strtol(comma + 1, &end, 10); if (*end != '\0') - return EXIT_UNKNOWN; + return; /* Got two numbers, so consume the argument. */ *critical_days = (int) c; *warning_days = (int) w; ++*av; - return EXIT_OK; + return; } - return EXIT_UNKNOWN; + return; } static exit_value_t get_name_type_args(const struct test_info_s *test_info, @@ -566,17 +566,13 @@ static exit_value_t test_certificate_valid(const struct test_info_s *test_info, int warning_days; int critical_days; - /* Is first arg the threshold? */ - if (get_cert_valid_thresholds(&av, &critical_days, &warning_days) != EXIT_OK) { - if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) - return xit; + get_cert_valid_thresholds(&av, &critical_days, &warning_days); - if (*av) - get_cert_valid_thresholds(&av, &critical_days, &warning_days); - } + if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) + return xit; if (*av) { - fputs("cert-valid takes arguments [ []] [warn-days,crit-days]", + fputs("cert-valid takes arguments [warn-days,crit-days] [ []]", test_info->errout); return EXIT_UNKNOWN; } From 379662a3f3e3ba29bdba2777ffc294375a189928 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Sun, 14 Jan 2018 13:41:44 +0000 Subject: [PATCH 05/47] Add plain lookup test. --- src/tools/getdns_server_mon.c | 37 ++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index a2d5bd5e..0baba280 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -161,7 +161,7 @@ static void usage() "Usage: " APP_NAME " [-MEr] @upstream testname [] []\n" " -M|--monitoring Make output suitable for monitoring tools\n" " -E|--fail-on-dns-errors Fail on DNS error (NXDOMAIN, SERVFAIL)\n" -" -T|--tls Use TLS transport\n" +" -T|--tls Use TLS transport\n" " -S|--strict-usage-profile Use strict profile (require authentication)\n" " -K|--spki-pin SPKI pin for TLS connections (can repeat)\n" " -v|--verbose Increase output verbosity\n" @@ -176,6 +176,7 @@ static void usage() "tsig spec: [:]:\n" "\n" "Tests:\n" +" lookup [ []] Check lookup on server\n" " auth [ []] Check authentication of TLS server\n" " If both a SPKI pin and authentication name are\n" " provided, both must authenticate for this test\n" @@ -518,6 +519,39 @@ static exit_value_t check_answer_type(const struct test_info_s *test_info, ** Test routines. **/ +static exit_value_t test_lookup(const struct test_info_s *test_info, + char ** av) +{ + const char *lookup_name = DEFAULT_LOOKUP_NAME; + uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; + exit_value_t xit; + + if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) + return xit; + + if (*av) { + fputs("lookup takes arguments [ []]", + test_info->errout); + return EXIT_UNKNOWN; + } + + getdns_dict *response; + if ((xit = search(test_info, lookup_name, lookup_type, &response)) != EXIT_OK) + return xit; + + if ((xit = check_result(test_info, response)) != EXIT_OK) + return xit; + + if ((xit = get_report_info(test_info, response, NULL, NULL, NULL)) != EXIT_OK) + return xit; + + if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) + return xit; + + fputs("lookup succeeded", test_info->errout); + return EXIT_OK; +} + static exit_value_t test_authenticate(const struct test_info_s *test_info, char ** av) { @@ -710,6 +744,7 @@ static struct test_funcs_s exit_value_t (*func)(const struct test_info_s *test_info, char **av); } TESTS[] = { + { "lookup", test_lookup }, { "auth", test_authenticate }, { "cert-valid", test_certificate_valid }, { "qname-min", test_qname_minimisation }, From 3258fdfd5aae043d4b19bcd534ecaffd3890705c Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Sun, 14 Jan 2018 23:28:55 +0000 Subject: [PATCH 06/47] Tabs? Spaces? Currently both, switch to spaces only. --- src/tools/getdns_server_mon.c | 436 +++++++++++++++++----------------- 1 file changed, 218 insertions(+), 218 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 0baba280..40ffbe9b 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -205,70 +205,70 @@ static void version() **/ static void get_cert_valid_thresholds(char ***av, - int *critical_days, - int *warning_days) + int *critical_days, + int *warning_days) { - *critical_days = CERT_EXPIRY_CRITICAL_DAYS; - *warning_days = CERT_EXPIRY_WARNING_DAYS; + *critical_days = CERT_EXPIRY_CRITICAL_DAYS; + *warning_days = CERT_EXPIRY_WARNING_DAYS; - if (**av) { - char *comma = strchr(**av, ','); - if (!comma) - return; + if (**av) { + char *comma = strchr(**av, ','); + if (!comma) + return; - char *end; - long w,c; + char *end; + long w,c; - c = strtol(**av, &end, 10); - /* - * If the number doesn't end at a comma, this isn't a - * properly formatted thresholds arg. Pass over it. - */ - if (end != comma) - return; + c = strtol(**av, &end, 10); + /* + * If the number doesn't end at a comma, this isn't a + * properly formatted thresholds arg. Pass over it. + */ + if (end != comma) + return; - /* - * Similarly, if the number doesn't end at the end of the - * argument, this isn't a properly formatted arg. - */ - w = strtol(comma + 1, &end, 10); - if (*end != '\0') - return; + /* + * Similarly, if the number doesn't end at the end of the + * argument, this isn't a properly formatted arg. + */ + w = strtol(comma + 1, &end, 10); + if (*end != '\0') + return; - /* Got two numbers, so consume the argument. */ - *critical_days = (int) c; - *warning_days = (int) w; - ++*av; - return; - } + /* Got two numbers, so consume the argument. */ + *critical_days = (int) c; + *warning_days = (int) w; + ++*av; + return; + } - return; + return; } static exit_value_t get_name_type_args(const struct test_info_s *test_info, - char ***av, - const char **lookup_name, - uint32_t *lookup_type) + char ***av, + const char **lookup_name, + uint32_t *lookup_type) { - if (**av) { - if (strlen(**av) > 0) { - *lookup_name = **av; - } else { - fputs("Empty name not valid", test_info->errout); - return EXIT_UNKNOWN; - } - ++*av; + if (**av) { + if (strlen(**av) > 0) { + *lookup_name = **av; + } else { + fputs("Empty name not valid", test_info->errout); + return EXIT_UNKNOWN; + } + ++*av; - if (**av) { - int rrtype = get_rrtype(**av); - if (rrtype >= 0) { - *lookup_type = (uint32_t) rrtype; - ++*av; - } - } - } + if (**av) { + int rrtype = get_rrtype(**av); + if (rrtype >= 0) { + *lookup_type = (uint32_t) rrtype; + ++*av; + } + } + } - return EXIT_OK; + return EXIT_OK; } static exit_value_t search(const struct test_info_s *test_info, @@ -445,73 +445,73 @@ static exit_value_t get_report_info(const struct test_info_s *test_info, } static exit_value_t get_answers(const struct test_info_s *test_info, - const getdns_dict *response, - getdns_list **answers, - size_t *no_answers) + const getdns_dict *response, + getdns_list **answers, + size_t *no_answers) { - getdns_return_t ret; + getdns_return_t ret; - if ((ret = getdns_dict_get_list(response, "/replies_tree/0/answer", answers)) != GETDNS_RETURN_GOOD) { + if ((ret = getdns_dict_get_list(response, "/replies_tree/0/answer", answers)) != GETDNS_RETURN_GOOD) { fprintf(test_info->errout, "Cannot get answers: %s (%d)", getdns_get_errorstr_by_id(ret), ret); return EXIT_UNKNOWN; - } + } - if ((ret = getdns_list_get_length(*answers, no_answers)) != GETDNS_RETURN_GOOD) { + if ((ret = getdns_list_get_length(*answers, no_answers)) != GETDNS_RETURN_GOOD) { fprintf(test_info->errout, "Cannot get number of answers: %s (%d)", getdns_get_errorstr_by_id(ret), ret); return EXIT_UNKNOWN; - } - if (*no_answers <= 0) { - fputs("Got zero answers", test_info->errout); - return EXIT_WARNING; - } + } + if (*no_answers <= 0) { + fputs("Got zero answers", test_info->errout); + return EXIT_WARNING; + } - return EXIT_OK; + return EXIT_OK; } static exit_value_t check_answer_type(const struct test_info_s *test_info, - const getdns_dict *response, - uint32_t rrtype) + const getdns_dict *response, + uint32_t rrtype) { - getdns_list *answers; - size_t no_answers; - exit_value_t xit; + getdns_list *answers; + size_t no_answers; + exit_value_t xit; - if ((xit = get_answers(test_info, response, &answers, &no_answers)) != EXIT_OK) - return xit; + if ((xit = get_answers(test_info, response, &answers, &no_answers)) != EXIT_OK) + return xit; - for (size_t i = 0; i < no_answers; ++i) { - getdns_dict *answer; - getdns_return_t ret; + for (size_t i = 0; i < no_answers; ++i) { + getdns_dict *answer; + getdns_return_t ret; - if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get answer number %zu: %s (%d)", - i, - getdns_get_errorstr_by_id(ret), - ret); - return EXIT_UNKNOWN; - } + if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get answer number %zu: %s (%d)", + i, + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } - uint32_t rtype; + uint32_t rtype; - if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get answer type: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); - return EXIT_UNKNOWN; - } - if (rtype == rrtype) - return EXIT_OK; - } + if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get answer type: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + if (rtype == rrtype) + return EXIT_OK; + } - fputs("Answer does not contain expected type", test_info->errout); + fputs("Answer does not contain expected type", test_info->errout); return EXIT_UNKNOWN; } @@ -520,14 +520,14 @@ static exit_value_t check_answer_type(const struct test_info_s *test_info, **/ static exit_value_t test_lookup(const struct test_info_s *test_info, - char ** av) + char ** av) { - const char *lookup_name = DEFAULT_LOOKUP_NAME; - uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; - exit_value_t xit; + const char *lookup_name = DEFAULT_LOOKUP_NAME; + uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; + exit_value_t xit; - if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) - return xit; + if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) + return xit; if (*av) { fputs("lookup takes arguments [ []]", @@ -545,22 +545,22 @@ static exit_value_t test_lookup(const struct test_info_s *test_info, if ((xit = get_report_info(test_info, response, NULL, NULL, NULL)) != EXIT_OK) return xit; - if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) - return xit; + if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) + return xit; - fputs("lookup succeeded", test_info->errout); - return EXIT_OK; + fputs("lookup succeeded", test_info->errout); + return EXIT_OK; } static exit_value_t test_authenticate(const struct test_info_s *test_info, - char ** av) + char ** av) { - const char *lookup_name = DEFAULT_LOOKUP_NAME; - uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; - exit_value_t xit; + const char *lookup_name = DEFAULT_LOOKUP_NAME; + uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; + exit_value_t xit; - if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) - return xit; + if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) + return xit; if (*av) { fputs("auth takes arguments [ []]", @@ -575,35 +575,35 @@ static exit_value_t test_authenticate(const struct test_info_s *test_info, if ((xit = check_result(test_info, response)) != EXIT_OK) return xit; - getdns_bindata *auth_status; + getdns_bindata *auth_status; if ((xit = get_report_info(test_info, response, NULL, &auth_status, NULL)) != EXIT_OK) return xit; - if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) - return xit; + if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) + return xit; - if (!auth_status || strcmp((char *) auth_status->data, "Success") != 0) { - fputs("Authentication failed", test_info->errout); - return EXIT_CRITICAL; - } else { - fputs("Authentication succeeded", test_info->errout); - return EXIT_OK; - } + if (!auth_status || strcmp((char *) auth_status->data, "Success") != 0) { + fputs("Authentication failed", test_info->errout); + return EXIT_CRITICAL; + } else { + fputs("Authentication succeeded", test_info->errout); + return EXIT_OK; + } } static exit_value_t test_certificate_valid(const struct test_info_s *test_info, - char **av) + char **av) { - const char *lookup_name = DEFAULT_LOOKUP_NAME; - uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; - exit_value_t xit; - int warning_days; - int critical_days; + const char *lookup_name = DEFAULT_LOOKUP_NAME; + uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; + exit_value_t xit; + int warning_days; + int critical_days; - get_cert_valid_thresholds(&av, &critical_days, &warning_days); + get_cert_valid_thresholds(&av, &critical_days, &warning_days); - if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) - return xit; + if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) + return xit; if (*av) { fputs("cert-valid takes arguments [warn-days,crit-days] [ []]", @@ -618,43 +618,43 @@ static exit_value_t test_certificate_valid(const struct test_info_s *test_info, if ((xit = check_result(test_info, response)) != EXIT_OK) return xit; - time_t expire_time; + time_t expire_time; if ((xit = get_report_info(test_info, response, NULL, NULL, &expire_time)) != EXIT_OK) return xit; - if (expire_time == 0) { - fputs("No PKIX certificate", test_info->errout); - return EXIT_CRITICAL; - } + if (expire_time == 0) { + fputs("No PKIX certificate", test_info->errout); + return EXIT_CRITICAL; + } - if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) - return xit; + if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) + return xit; - time_t now = time(NULL); - int days_to_expiry = (expire_time - now) / 86400; + time_t now = time(NULL); + int days_to_expiry = (expire_time - now) / 86400; - if (days_to_expiry < 0) { - fprintf(test_info->errout, - "Certificate expired %d day%s ago", - -days_to_expiry, - (days_to_expiry < -1) ? "s" : ""); - return EXIT_CRITICAL; - } - if (days_to_expiry == 0) { - fputs("Certificate expires today", test_info->errout); - return EXIT_CRITICAL; - } - fprintf(test_info->errout, - "Certificate will expire in %d day%s", - days_to_expiry, - (days_to_expiry > 1) ? "s" : ""); - if (days_to_expiry <= critical_days) { - return EXIT_CRITICAL; - } - if (days_to_expiry <= warning_days) { - return EXIT_WARNING; - } - return EXIT_OK; + if (days_to_expiry < 0) { + fprintf(test_info->errout, + "Certificate expired %d day%s ago", + -days_to_expiry, + (days_to_expiry < -1) ? "s" : ""); + return EXIT_CRITICAL; + } + if (days_to_expiry == 0) { + fputs("Certificate expires today", test_info->errout); + return EXIT_CRITICAL; + } + fprintf(test_info->errout, + "Certificate will expire in %d day%s", + days_to_expiry, + (days_to_expiry > 1) ? "s" : ""); + if (days_to_expiry <= critical_days) { + return EXIT_CRITICAL; + } + if (days_to_expiry <= warning_days) { + return EXIT_WARNING; + } + return EXIT_OK; } static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, @@ -680,61 +680,61 @@ static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, if (xit != EXIT_OK) return xit; - getdns_list *answers; - size_t no_answers; + getdns_list *answers; + size_t no_answers; - if ((xit = get_answers(test_info, response, &answers, &no_answers)) != EXIT_OK) - return xit; + if ((xit = get_answers(test_info, response, &answers, &no_answers)) != EXIT_OK) + return xit; - for (size_t i = 0; i < no_answers; ++i) { - getdns_dict *answer; - getdns_return_t ret; + for (size_t i = 0; i < no_answers; ++i) { + getdns_dict *answer; + getdns_return_t ret; - if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get answer number %zu: %s (%d)", - i, - getdns_get_errorstr_by_id(ret), - ret); - return EXIT_UNKNOWN; - } + if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get answer number %zu: %s (%d)", + i, + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } - uint32_t rtype; + uint32_t rtype; - if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get answer type: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); - return EXIT_UNKNOWN; - } - if (rtype != GETDNS_RRTYPE_TXT) - continue; + if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get answer type: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + if (rtype != GETDNS_RRTYPE_TXT) + continue; - getdns_bindata *rtxt; - if ((ret = getdns_dict_get_bindata(answer, "/rdata/txt_strings/0", &rtxt)) != GETDNS_RETURN_GOOD) { - fputs("No answer text", test_info->errout); - return EXIT_WARNING; - } + getdns_bindata *rtxt; + if ((ret = getdns_dict_get_bindata(answer, "/rdata/txt_strings/0", &rtxt)) != GETDNS_RETURN_GOOD) { + fputs("No answer text", test_info->errout); + return EXIT_WARNING; + } - if (rtxt->size > 0 ) { - switch(rtxt->data[0]) { - case 'H': - fputs("QNAME minimisation ON", test_info->errout); - return EXIT_OK; + if (rtxt->size > 0 ) { + switch(rtxt->data[0]) { + case 'H': + fputs("QNAME minimisation ON", test_info->errout); + return EXIT_OK; - case 'N': - fputs("QNAME minimisation OFF", test_info->errout); - return EXIT_WARNING; + case 'N': + fputs("QNAME minimisation OFF", test_info->errout); + return EXIT_WARNING; - default: - /* Unrecognised message. */ - break; - } - } - } + default: + /* Unrecognised message. */ + break; + } + } + } - fputs("No valid QNAME minimisation data", test_info->errout); + fputs("No valid QNAME minimisation data", test_info->errout); return EXIT_UNKNOWN; } @@ -757,7 +757,7 @@ int main(int ATTR_UNUSED(ac), char *av[]) getdns_list *pinset = NULL; size_t pinset_size = 0; bool strict_usage_profile = false; - bool use_tls = false; + bool use_tls = false; test_info.errout = stderr; atexit(exit_tidy); @@ -783,7 +783,7 @@ int main(int ATTR_UNUSED(ac), char *av[]) } else if (strcmp(*av, "-S") == 0 || strcmp(*av, "--strict-usage-profile") == 0 ) { strict_usage_profile = true; - use_tls = true; + use_tls = true; } else if (strcmp(*av, "-K") == 0 || strcmp(*av, "--spki-pin") == 0 ) { ++av; @@ -816,7 +816,7 @@ int main(int ATTR_UNUSED(ac), char *av[]) exit(EXIT_UNKNOWN); } - use_tls = true; + use_tls = true; } else if (strcmp(*av, "-v") == 0 || strcmp(*av, "--verbose") == 0) { ++test_info.verbosity; @@ -903,16 +903,16 @@ int main(int ATTR_UNUSED(ac), char *av[]) } /* Set other context parameters. */ - if (use_tls) { - getdns_transport_list_t t[] = { GETDNS_TRANSPORT_TLS }; - if ((ret = getdns_context_set_dns_transport_list(test_info.context, 1, t)) != GETDNS_RETURN_GOOD) { - fprintf(test_info.errout, - "Unable to set TLS transport: %s (%d)\n", - getdns_get_errorstr_by_id(ret), - ret); - exit(EXIT_UNKNOWN); - } - } + if (use_tls) { + getdns_transport_list_t t[] = { GETDNS_TRANSPORT_TLS }; + if ((ret = getdns_context_set_dns_transport_list(test_info.context, 1, t)) != GETDNS_RETURN_GOOD) { + fprintf(test_info.errout, + "Unable to set TLS transport: %s (%d)\n", + getdns_get_errorstr_by_id(ret), + ret); + exit(EXIT_UNKNOWN); + } + } if (strict_usage_profile) { ret = getdns_context_set_tls_authentication(test_info.context, GETDNS_AUTHENTICATION_REQUIRED); From b9312e790f82c88fa6a3860b50b1102e9ff739e7 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 15 Jan 2018 10:01:01 +0000 Subject: [PATCH 07/47] Correct certificate expiry custom threshold handling. --- src/tools/getdns_server_mon.c | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 40ffbe9b..bc49608e 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -204,13 +204,10 @@ static void version() ** Functions used by tests. **/ -static void get_cert_valid_thresholds(char ***av, - int *critical_days, - int *warning_days) +static void get_thresholds(char ***av, + int *critical, + int *warning) { - *critical_days = CERT_EXPIRY_CRITICAL_DAYS; - *warning_days = CERT_EXPIRY_WARNING_DAYS; - if (**av) { char *comma = strchr(**av, ','); if (!comma) @@ -219,7 +216,7 @@ static void get_cert_valid_thresholds(char ***av, char *end; long w,c; - c = strtol(**av, &end, 10); + w = strtol(**av, &end, 10); /* * If the number doesn't end at a comma, this isn't a * properly formatted thresholds arg. Pass over it. @@ -231,13 +228,13 @@ static void get_cert_valid_thresholds(char ***av, * Similarly, if the number doesn't end at the end of the * argument, this isn't a properly formatted arg. */ - w = strtol(comma + 1, &end, 10); + c = strtol(comma + 1, &end, 10); if (*end != '\0') return; /* Got two numbers, so consume the argument. */ - *critical_days = (int) c; - *warning_days = (int) w; + *critical = (int) c; + *warning = (int) w; ++*av; return; } @@ -597,10 +594,10 @@ static exit_value_t test_certificate_valid(const struct test_info_s *test_info, const char *lookup_name = DEFAULT_LOOKUP_NAME; uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; exit_value_t xit; - int warning_days; - int critical_days; + int warning_days = CERT_EXPIRY_WARNING_DAYS; + int critical_days = CERT_EXPIRY_CRITICAL_DAYS; - get_cert_valid_thresholds(&av, &critical_days, &warning_days); + get_thresholds(&av, &critical_days, &warning_days); if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) return xit; @@ -642,12 +639,12 @@ static exit_value_t test_certificate_valid(const struct test_info_s *test_info, } if (days_to_expiry == 0) { fputs("Certificate expires today", test_info->errout); - return EXIT_CRITICAL; + } else { + fprintf(test_info->errout, + "Certificate will expire in %d day%s", + days_to_expiry, + (days_to_expiry > 1) ? "s" : ""); } - fprintf(test_info->errout, - "Certificate will expire in %d day%s", - days_to_expiry, - (days_to_expiry > 1) ? "s" : ""); if (days_to_expiry <= critical_days) { return EXIT_CRITICAL; } From 5d4bc8bc96b4b12c9e679049d17296bd69c9c853 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 15 Jan 2018 10:16:26 +0000 Subject: [PATCH 08/47] Add rtt test. --- src/tools/getdns_server_mon.c | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index bc49608e..36666ac9 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -46,6 +46,9 @@ #define APP_NAME "getdns_server_mon" +#define RTT_CRITICAL_MS 250 +#define RTT_WARNING_MS 500 + #define CERT_EXPIRY_CRITICAL_DAYS 7 #define CERT_EXPIRY_WARNING_DAYS 14 @@ -177,6 +180,8 @@ static void usage() "\n" "Tests:\n" " lookup [ []] Check lookup on server\n" +" rtt [warn-ms,crit-ms] [ []]\n" +" Check server round trip time (default 500,250)\n" " auth [ []] Check authentication of TLS server\n" " If both a SPKI pin and authentication name are\n" " provided, both must authenticate for this test\n" @@ -549,6 +554,49 @@ static exit_value_t test_lookup(const struct test_info_s *test_info, return EXIT_OK; } +static exit_value_t test_rtt(const struct test_info_s *test_info, + char ** av) +{ + const char *lookup_name = DEFAULT_LOOKUP_NAME; + uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; + exit_value_t xit; + int critical_ms = RTT_CRITICAL_MS; + int warning_ms = RTT_WARNING_MS; + uint32_t rtt_val; + + get_thresholds(&av, &critical_ms, &warning_ms); + + if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) + return xit; + + if (*av) { + fputs("lookup takes arguments [ []]", + test_info->errout); + return EXIT_UNKNOWN; + } + + getdns_dict *response; + if ((xit = search(test_info, lookup_name, lookup_type, &response)) != EXIT_OK) + return xit; + + if ((xit = check_result(test_info, response)) != EXIT_OK) + return xit; + + if ((xit = get_report_info(test_info, response, &rtt_val, NULL, NULL)) != EXIT_OK) + return xit; + + if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) + return xit; + + fputs("lookup succeeded", test_info->errout); + + if ((int) rtt_val > critical_ms) + return EXIT_CRITICAL; + else if ((int) rtt_val > warning_ms) + return EXIT_WARNING; + return EXIT_OK; +} + static exit_value_t test_authenticate(const struct test_info_s *test_info, char ** av) { @@ -742,6 +790,7 @@ static struct test_funcs_s } TESTS[] = { { "lookup", test_lookup }, + { "rtt", test_rtt }, { "auth", test_authenticate }, { "cert-valid", test_certificate_valid }, { "qname-min", test_qname_minimisation }, From c0d7d2c27916497022098e58a0f02e655c792191 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 15 Jan 2018 10:21:39 +0000 Subject: [PATCH 09/47] Print exit status at end of main output line. --- src/tools/getdns_server_mon.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 36666ac9..3275a3b1 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -983,6 +983,24 @@ int main(int ATTR_UNUSED(ac), char *av[]) ++f) { if (strcmp(testname, f->name) == 0) { exit_value_t xit = f->func(&test_info, av); + switch(xit) { + case 0: + fputs(" OK", test_info.errout); + break; + + case 1: + fputs(" WARNING", test_info.errout); + break; + + case 2: + fputs(" CRITICAL", test_info.errout); + break; + + default: + fputs(" UNKNOWN", test_info.errout); + break; + + } fputc('\n', test_info.errout); exit(xit); } From 22996bf07d4c20eefa9c97cc9c6added2941332c Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 15 Jan 2018 11:00:12 +0000 Subject: [PATCH 10/47] If TLS auth name given, lookup is to go over TLS. --- src/tools/getdns_server_mon.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 3275a3b1..e68651c4 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -889,6 +889,12 @@ int main(int ATTR_UNUSED(ac), char *av[]) ret); exit(EXIT_UNKNOWN); } + + /* If the resolver info include TLS auth name, use TLS. */ + getdns_bindata *tls_auth_name; + if (getdns_dict_get_bindata(resolver, "tls_auth_name", &tls_auth_name) == GETDNS_RETURN_GOOD) + use_tls = true; + if ((ret = getdns_dict_get_bindata(resolver, "address_data", &address)) != GETDNS_RETURN_GOOD) { fprintf(test_info.errout, "\"%s\" did not translate to an IP dict: %s (%d)\n", From 77a5a15cdf687b8070f0389cfe12ef5173924216 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 15 Jan 2018 11:00:38 +0000 Subject: [PATCH 11/47] Minor output corrections. --- src/tools/getdns_server_mon.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index e68651c4..8be35aff 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -550,7 +550,7 @@ static exit_value_t test_lookup(const struct test_info_s *test_info, if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) return xit; - fputs("lookup succeeded", test_info->errout); + fputs("Lookup succeeded", test_info->errout); return EXIT_OK; } @@ -570,7 +570,7 @@ static exit_value_t test_rtt(const struct test_info_s *test_info, return xit; if (*av) { - fputs("lookup takes arguments [ []]", + fputs("rtt takes arguments [warn-ms,crit-ms] [ []]", test_info->errout); return EXIT_UNKNOWN; } @@ -588,7 +588,7 @@ static exit_value_t test_rtt(const struct test_info_s *test_info, if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) return xit; - fputs("lookup succeeded", test_info->errout); + fputs("RTT lookup succeeded", test_info->errout); if ((int) rtt_val > critical_ms) return EXIT_CRITICAL; From cb7af3348890712847960f27b6c896c3f72e9f3c Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 15 Jan 2018 11:28:11 +0000 Subject: [PATCH 12/47] Some tests imply TLS. Explicitly make sure these always go over TLS. --- src/tools/getdns_server_mon.c | 88 ++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 8be35aff..7dc59b17 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -786,15 +786,16 @@ static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, static struct test_funcs_s { const char *name; + bool implies_tls; exit_value_t (*func)(const struct test_info_s *test_info, char **av); } TESTS[] = { - { "lookup", test_lookup }, - { "rtt", test_rtt }, - { "auth", test_authenticate }, - { "cert-valid", test_certificate_valid }, - { "qname-min", test_qname_minimisation }, - { NULL, NULL } + { "lookup", false, test_lookup }, + { "rtt", false, test_rtt }, + { "auth", true, test_authenticate }, + { "cert-valid", true, test_certificate_valid }, + { "qname-min", false, test_qname_minimisation }, + { NULL, false, NULL } }; int main(int ATTR_UNUSED(ac), char *av[]) @@ -955,17 +956,6 @@ int main(int ATTR_UNUSED(ac), char *av[]) } /* Set other context parameters. */ - if (use_tls) { - getdns_transport_list_t t[] = { GETDNS_TRANSPORT_TLS }; - if ((ret = getdns_context_set_dns_transport_list(test_info.context, 1, t)) != GETDNS_RETURN_GOOD) { - fprintf(test_info.errout, - "Unable to set TLS transport: %s (%d)\n", - getdns_get_errorstr_by_id(ret), - ret); - exit(EXIT_UNKNOWN); - } - } - if (strict_usage_profile) { ret = getdns_context_set_tls_authentication(test_info.context, GETDNS_AUTHENTICATION_REQUIRED); if (ret != GETDNS_RETURN_GOOD) { @@ -984,33 +974,47 @@ int main(int ATTR_UNUSED(ac), char *av[]) usage(); ++av; - for (const struct test_funcs_s *f = TESTS; - f->name != NULL; - ++f) { - if (strcmp(testname, f->name) == 0) { - exit_value_t xit = f->func(&test_info, av); - switch(xit) { - case 0: - fputs(" OK", test_info.errout); - break; + const struct test_funcs_s *f; + for (f = TESTS; f->name != NULL; ++f) { + if (strcmp(testname, f->name) == 0) + break; + } - case 1: - fputs(" WARNING", test_info.errout); - break; + if (f->name == NULL) { + fprintf(test_info.errout, "Unknown test %s\n", testname); + exit(EXIT_UNKNOWN); + } - case 2: - fputs(" CRITICAL", test_info.errout); - break; - - default: - fputs(" UNKNOWN", test_info.errout); - break; - - } - fputc('\n', test_info.errout); - exit(xit); + if (use_tls || f->implies_tls) { + getdns_transport_list_t t[] = { GETDNS_TRANSPORT_TLS }; + if ((ret = getdns_context_set_dns_transport_list(test_info.context, 1, t)) != GETDNS_RETURN_GOOD) { + fprintf(test_info.errout, + "Unable to set TLS transport: %s (%d)\n", + getdns_get_errorstr_by_id(ret), + ret); + exit(EXIT_UNKNOWN); } } - fprintf(test_info.errout, "Unknown test %s\n", testname); - exit(EXIT_UNKNOWN); + + exit_value_t xit = f->func(&test_info, av); + switch(xit) { + case 0: + fputs(" OK", test_info.errout); + break; + + case 1: + fputs(" WARNING", test_info.errout); + break; + + case 2: + fputs(" CRITICAL", test_info.errout); + break; + + default: + fputs(" UNKNOWN", test_info.errout); + break; + + } + fputc('\n', test_info.errout); + exit(xit); } From 3298b5cd508f921e89c8f0147622c66252e408e7 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 15 Jan 2018 12:37:57 +0000 Subject: [PATCH 13/47] Extract common processing into search_check() and parse_search_check(). --- src/tools/getdns_server_mon.c | 175 ++++++++++++++++------------------ 1 file changed, 84 insertions(+), 91 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 7dc59b17..ee39f495 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -517,12 +517,41 @@ static exit_value_t check_answer_type(const struct test_info_s *test_info, return EXIT_UNKNOWN; } -/** - ** Test routines. - **/ +static exit_value_t search_check(const struct test_info_s *test_info, + const char *lookup_name, + uint16_t lookup_type, + getdns_dict **response, + uint32_t *rtt, + getdns_bindata **auth_status, + time_t *cert_expire_time) +{ + exit_value_t xit; + getdns_dict *resp; -static exit_value_t test_lookup(const struct test_info_s *test_info, - char ** av) + if ((xit = search(test_info, lookup_name, lookup_type, &resp)) != EXIT_OK) + return xit; + + if ((xit = check_result(test_info, resp)) != EXIT_OK) + return xit; + + if ((xit = get_report_info(test_info, resp, rtt, auth_status, cert_expire_time)) != EXIT_OK) + return xit; + + if ((xit = check_answer_type(test_info, resp, lookup_type)) != EXIT_OK) + return xit; + + if (response) + *response = resp; + return xit; +} + +static exit_value_t parse_search_check(const struct test_info_s *test_info, + char **av, + const char *usage, + getdns_dict **response, + uint32_t *rtt, + getdns_bindata **auth_status, + time_t *cert_expire_time) { const char *lookup_name = DEFAULT_LOOKUP_NAME; uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; @@ -532,22 +561,32 @@ static exit_value_t test_lookup(const struct test_info_s *test_info, return xit; if (*av) { - fputs("lookup takes arguments [ []]", - test_info->errout); + fputs(usage, test_info->errout); return EXIT_UNKNOWN; } - getdns_dict *response; - if ((xit = search(test_info, lookup_name, lookup_type, &response)) != EXIT_OK) - return xit; + return search_check(test_info, + lookup_name, lookup_type, + response, + rtt, auth_status, cert_expire_time); +} - if ((xit = check_result(test_info, response)) != EXIT_OK) - return xit; +/** + ** Test routines. + **/ - if ((xit = get_report_info(test_info, response, NULL, NULL, NULL)) != EXIT_OK) - return xit; +static exit_value_t test_lookup(const struct test_info_s *test_info, + char ** av) +{ + exit_value_t xit; - if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) + if ((xit = parse_search_check(test_info, + av, + "lookup takes arguments [ []]", + NULL, + NULL, + NULL, + NULL)) != EXIT_OK) return xit; fputs("Lookup succeeded", test_info->errout); @@ -557,8 +596,6 @@ static exit_value_t test_lookup(const struct test_info_s *test_info, static exit_value_t test_rtt(const struct test_info_s *test_info, char ** av) { - const char *lookup_name = DEFAULT_LOOKUP_NAME; - uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; exit_value_t xit; int critical_ms = RTT_CRITICAL_MS; int warning_ms = RTT_WARNING_MS; @@ -566,26 +603,13 @@ static exit_value_t test_rtt(const struct test_info_s *test_info, get_thresholds(&av, &critical_ms, &warning_ms); - if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) - return xit; - - if (*av) { - fputs("rtt takes arguments [warn-ms,crit-ms] [ []]", - test_info->errout); - return EXIT_UNKNOWN; - } - - getdns_dict *response; - if ((xit = search(test_info, lookup_name, lookup_type, &response)) != EXIT_OK) - return xit; - - if ((xit = check_result(test_info, response)) != EXIT_OK) - return xit; - - if ((xit = get_report_info(test_info, response, &rtt_val, NULL, NULL)) != EXIT_OK) - return xit; - - if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) + if ((xit = parse_search_check(test_info, + av, + "rtt takes arguments [warn-ms,crit-ms] [ []]", + NULL, + &rtt_val, + NULL, + NULL)) != EXIT_OK) return xit; fputs("RTT lookup succeeded", test_info->errout); @@ -600,31 +624,16 @@ static exit_value_t test_rtt(const struct test_info_s *test_info, static exit_value_t test_authenticate(const struct test_info_s *test_info, char ** av) { - const char *lookup_name = DEFAULT_LOOKUP_NAME; - uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; exit_value_t xit; - - if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) - return xit; - - if (*av) { - fputs("auth takes arguments [ []]", - test_info->errout); - return EXIT_UNKNOWN; - } - - getdns_dict *response; - if ((xit = search(test_info, lookup_name, lookup_type, &response)) != EXIT_OK) - return xit; - - if ((xit = check_result(test_info, response)) != EXIT_OK) - return xit; - getdns_bindata *auth_status; - if ((xit = get_report_info(test_info, response, NULL, &auth_status, NULL)) != EXIT_OK) - return xit; - if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) + if ((xit = parse_search_check(test_info, + av, + "auth takes arguments [ []]", + NULL, + NULL, + &auth_status, + NULL)) != EXIT_OK) return xit; if (!auth_status || strcmp((char *) auth_status->data, "Success") != 0) { @@ -639,42 +648,28 @@ static exit_value_t test_authenticate(const struct test_info_s *test_info, static exit_value_t test_certificate_valid(const struct test_info_s *test_info, char **av) { - const char *lookup_name = DEFAULT_LOOKUP_NAME; - uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; exit_value_t xit; int warning_days = CERT_EXPIRY_WARNING_DAYS; int critical_days = CERT_EXPIRY_CRITICAL_DAYS; + time_t expire_time; get_thresholds(&av, &critical_days, &warning_days); - if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) + if ((xit = parse_search_check(test_info, + av, + "cert-valid takes arguments [warn-days,crit-days] [ []]", + NULL, + NULL, + NULL, + &expire_time)) != EXIT_OK) return xit; - if (*av) { - fputs("cert-valid takes arguments [warn-days,crit-days] [ []]", - test_info->errout); - return EXIT_UNKNOWN; - } - - getdns_dict *response; - if ((xit = search(test_info, lookup_name, lookup_type, &response)) != EXIT_OK) - return xit; - - if ((xit = check_result(test_info, response)) != EXIT_OK) - return xit; - - time_t expire_time; - if ((xit = get_report_info(test_info, response, NULL, NULL, &expire_time)) != EXIT_OK) - return xit; if (expire_time == 0) { fputs("No PKIX certificate", test_info->errout); return EXIT_CRITICAL; } - if ((xit = check_answer_type(test_info, response, lookup_type)) != EXIT_OK) - return xit; - time_t now = time(NULL); int days_to_expiry = (expire_time - now) / 86400; @@ -712,17 +707,15 @@ static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, } getdns_dict *response; - exit_value_t xit = search(test_info, "qnamemintest.internet.nl", GETDNS_RRTYPE_TXT, &response); - if (xit != EXIT_OK) - return xit; + exit_value_t xit; - xit = check_result(test_info, response); - if (xit != EXIT_OK) - return xit; - - /* Don't need any of this, but do want check and verbosity reporting. */ - xit = get_report_info(test_info, response, NULL, NULL, NULL); - if (xit != EXIT_OK) + if ((xit = search_check(test_info, + "qnamemintest.internet.nl", + GETDNS_RRTYPE_TXT, + &response, + NULL, + NULL, + NULL)) != EXIT_OK) return xit; getdns_list *answers; From 08b5976f9cc8f6d91c4be27b09d999af763a326f Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 15 Jan 2018 13:19:48 +0000 Subject: [PATCH 14/47] Decouple from getdns config. This is now a pure getdns client. --- src/tools/getdns_server_mon.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index ee39f495..14882760 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -25,9 +25,6 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "config.h" -#include "debug.h" - #include #include #include @@ -200,8 +197,10 @@ static void usage() static void version() { - fputs(APP_NAME ": getdns " GETDNS_VERSION " , API " GETDNS_API_VERSION ".\n", - test_info.errout); + fprintf(test_info.errout, + APP_NAME ": getdns version %s, API version '%s'.\n", + getdns_get_version(), + getdns_get_api_version()); exit(EXIT_UNKNOWN); } @@ -791,7 +790,7 @@ static struct test_funcs_s { NULL, false, NULL } }; -int main(int ATTR_UNUSED(ac), char *av[]) +int main(int ac, char *av[]) { getdns_return_t ret; getdns_list *pinset = NULL; @@ -799,6 +798,8 @@ int main(int ATTR_UNUSED(ac), char *av[]) bool strict_usage_profile = false; bool use_tls = false; + (void) ac; + test_info.errout = stderr; atexit(exit_tidy); if ((ret = getdns_context_create(&test_info.context, 1)) != GETDNS_RETURN_GOOD) { From 3438c68591d4daf376b7a0d2e115cc93ee7ea8ea Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 15 Jan 2018 13:26:09 +0000 Subject: [PATCH 15/47] Prefix TLS-only options with 'tls-'. --- src/tools/getdns_server_mon.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 14882760..e21d1ae7 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -179,12 +179,13 @@ static void usage() " lookup [ []] Check lookup on server\n" " rtt [warn-ms,crit-ms] [ []]\n" " Check server round trip time (default 500,250)\n" -" auth [ []] Check authentication of TLS server\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" " provided, both must authenticate for this test\n" " to pass.\n" -" qname-min Check whether server supports QNAME minimisation\n" -" cert-valid [warn-days,crit-days] [ [type]]\n" +" tls_cert-valid [warn-days,crit-days] [ [type]]\n" " Check server certificate validity, report\n" " warning or critical if days to expiry at\n" " or below thresholds (default 14,7).\n" @@ -784,9 +785,9 @@ static struct test_funcs_s { { "lookup", false, test_lookup }, { "rtt", false, test_rtt }, - { "auth", true, test_authenticate }, - { "cert-valid", true, test_certificate_valid }, { "qname-min", false, test_qname_minimisation }, + { "tls-auth", true, test_authenticate }, + { "tls-cert-valid", true, test_certificate_valid }, { NULL, false, NULL } }; From 8dc3a84735e43234c1348aa5dbe7229905bc8b78 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 15 Jan 2018 17:42:43 +0000 Subject: [PATCH 16/47] Add options specifying transport. --- src/tools/getdns_server_mon.c | 61 ++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index e21d1ae7..1a3bf743 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -406,6 +406,33 @@ static exit_value_t get_report_info(const struct test_info_s *test_info, ret); return EXIT_UNKNOWN; } + if (test_info->verbosity >= VERBOSITY_ADDITIONAL) { + uint32_t transport; + if ((ret = getdns_dict_get_int(d, "transport", &transport)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get transport: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + switch(transport) { + case GETDNS_TRANSPORT_UDP: + fputs("UDP, ", test_info->errout); + break; + + case GETDNS_TRANSPORT_TCP: + fputs("TCP, ", test_info->errout); + break; + + case GETDNS_TRANSPORT_TLS: + fputs("TLS, ", test_info->errout); + break; + + default: + fputs("???, ", test_info->errout); + break; + } + } if ((ret = getdns_dict_get_int(d, "run_time/ms", &rtt_val)) != GETDNS_RETURN_GOOD) { fprintf(test_info->errout, "Cannot get RTT: %s (%d)", @@ -797,6 +824,8 @@ int main(int ac, char *av[]) getdns_list *pinset = NULL; size_t pinset_size = 0; bool strict_usage_profile = false; + bool use_udp = false; + bool use_tcp = false; bool use_tls = false; (void) ac; @@ -819,6 +848,12 @@ int main(int ac, char *av[]) } else if (strcmp(*av, "-E") == 0 || strcmp(*av, "--fail-on-dns-errors") == 0) { test_info.fail_on_dns_errors = true; + } else if (strcmp(*av, "-u") == 0 || + strcmp(*av, "--udp") == 0 ) { + use_udp = true; + } else if (strcmp(*av, "-t") == 0 || + strcmp(*av, "--tcp") == 0 ) { + use_tcp = true; } else if (strcmp(*av, "-T") == 0 || strcmp(*av, "--tls") == 0 ) { use_tls = true; @@ -980,11 +1015,29 @@ int main(int ac, char *av[]) exit(EXIT_UNKNOWN); } - if (use_tls || f->implies_tls) { - getdns_transport_list_t t[] = { GETDNS_TRANSPORT_TLS }; - if ((ret = getdns_context_set_dns_transport_list(test_info.context, 1, t)) != GETDNS_RETURN_GOOD) { + if (f->implies_tls) { + if (use_udp | use_tcp) { + fputs("Test requires TLS, or TLS authentication specified\n", test_info.errout); + exit(EXIT_UNKNOWN); + } + use_tls = true; + } + + if ((use_tls + use_udp + use_tcp) > 1) { + fputs("Specify one only of -u, -t, -T\n", test_info.errout); + exit(EXIT_UNKNOWN); + } + + if (use_tls || use_udp || use_tcp) { + getdns_transport_list_t udp[] = { GETDNS_TRANSPORT_UDP }; + getdns_transport_list_t tcp[] = { GETDNS_TRANSPORT_TCP }; + getdns_transport_list_t tls[] = { GETDNS_TRANSPORT_TLS }; + getdns_transport_list_t *transport = + (use_tls) ? tls : (use_tcp) ? tcp : udp; + if ((ret = getdns_context_set_dns_transport_list(test_info.context, 1, transport)) != GETDNS_RETURN_GOOD) { fprintf(test_info.errout, - "Unable to set TLS transport: %s (%d)\n", + "Unable to set %s transport: %s (%d)\n", + (use_tls) ? "TLS" : (use_tcp) ? "TCP" : "UDP", getdns_get_errorstr_by_id(ret), ret); exit(EXIT_UNKNOWN); From 5ea0edf2626d90118d0b204ed27e54f0a966b8fa Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 15 Jan 2018 17:42:57 +0000 Subject: [PATCH 17/47] Update usage. --- src/tools/getdns_server_mon.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 1a3bf743..3b0001ba 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -158,9 +158,12 @@ static void exit_tidy() static void usage() { fputs( -"Usage: " APP_NAME " [-MEr] @upstream testname [] []\n" +"Usage: " APP_NAME " [-M] [-E] [(-u|-t|-T)] [-S] [-K ]\n" +" [-v [-v [-v]]] [-V] @upstream testname [] []\n" " -M|--monitoring Make output suitable for monitoring tools\n" " -E|--fail-on-dns-errors Fail on DNS error (NXDOMAIN, SERVFAIL)\n" +" -u|--udp Use UDP transport\n" +" -t|--tcp Use TCP transport\n" " -T|--tls Use TLS transport\n" " -S|--strict-usage-profile Use strict profile (require authentication)\n" " -K|--spki-pin SPKI pin for TLS connections (can repeat)\n" From b8424e494d23311f7f18752be05b58f0562350a9 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 16 Jan 2018 11:05:16 +0000 Subject: [PATCH 18/47] Fix up some small usage typos, and don't report result if issuing test usage message. --- src/tools/getdns_server_mon.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 3b0001ba..85383317 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -59,7 +59,8 @@ typedef enum { EXIT_OK = 0, EXIT_WARNING, EXIT_CRITICAL, - EXIT_UNKNOWN + EXIT_UNKNOWN, + EXIT_USAGE /* Special case - internal only. */ } exit_value_t; /* Plugin verbosity values */ @@ -184,11 +185,11 @@ static void usage() " 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" +" tls-auth [ []] Check authentication of TLS server\n" " If both a SPKI pin and authentication name are\n" " provided, both must authenticate for this test\n" " to pass.\n" -" tls_cert-valid [warn-days,crit-days] [ [type]]\n" +" tls-cert-valid [warn-days,crit-days] [ [type]]\n" " Check server certificate validity, report\n" " warning or critical if days to expiry at\n" " or below thresholds (default 14,7).\n" @@ -592,7 +593,7 @@ static exit_value_t parse_search_check(const struct test_info_s *test_info, if (*av) { fputs(usage, test_info->errout); - return EXIT_UNKNOWN; + return EXIT_USAGE; } return search_check(test_info, @@ -733,7 +734,7 @@ static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, if (*av) { fputs("qname-min takes no arguments", test_info->errout); - return EXIT_UNKNOWN; + return EXIT_USAGE; } getdns_dict *response; @@ -1049,22 +1050,29 @@ int main(int ac, char *av[]) exit_value_t xit = f->func(&test_info, av); switch(xit) { - case 0: + case EXIT_OK: fputs(" OK", test_info.errout); break; - case 1: + case EXIT_WARNING: fputs(" WARNING", test_info.errout); break; - case 2: + case EXIT_CRITICAL: fputs(" CRITICAL", test_info.errout); break; - default: + case EXIT_UNKNOWN: fputs(" UNKNOWN", test_info.errout); break; + case EXIT_USAGE: + xit = EXIT_UNKNOWN; + break; + + default: + fputs(" ???", test_info.errout); + break; } fputc('\n', test_info.errout); exit(xit); From fdafb458efda0baf625b9067478a39b6f3795989 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 16 Jan 2018 12:19:33 +0000 Subject: [PATCH 19/47] Decide we don't want return_both_v4_and_v6 on queries. --- src/tools/getdns_server_mon.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 85383317..e66c7c9d 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -293,14 +293,6 @@ static exit_value_t search(const struct test_info_s *test_info, getdns_dict_destroy(extensions); return EXIT_UNKNOWN; } - if ((ret = getdns_dict_set_int(extensions, "return_both_v4_and_v6", GETDNS_EXTENSION_TRUE)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot set return both IPv4 and IPv6: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); - getdns_dict_destroy(extensions); - return EXIT_UNKNOWN; - } if (test_info->verbosity >= VERBOSITY_DEBUG) { fprintf(test_info->errout, From a4ff6de98559e4f8914e39770780888f18c6dcaf Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 16 Jan 2018 12:56:50 +0000 Subject: [PATCH 20/47] Add 'tls-padding' test. --- src/tools/getdns_server_mon.c | 152 ++++++++++++++++++++++++++++++++-- 1 file changed, 146 insertions(+), 6 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index e66c7c9d..811a6e4e 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -52,6 +52,8 @@ #define DEFAULT_LOOKUP_NAME "getdnsapi.net" #define DEFAULT_LOOKUP_TYPE GETDNS_RRTYPE_AAAA +#define EDNS0_PADDING_CODE 12 + #define EXAMPLE_PIN "pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\"" /* Plugin exit values */ @@ -193,6 +195,10 @@ static void usage() " Check server certificate validity, report\n" " warning or critical if days to expiry at\n" " or below thresholds (default 14,7).\n" +" tls-padding [ []]\n" +" Check server support for EDNS0 padding in TLS\n" +" Special blocksize values are 0 = off,\n" +" 1 = sensible default.\n" "\n" "Enabling monitoring mode ensures output messages and exit statuses conform\n" "to the requirements of monitoring plugins (www.monitoring-plugins.org).\n", @@ -471,14 +477,19 @@ static exit_value_t get_report_info(const struct test_info_s *test_info, static exit_value_t get_answers(const struct test_info_s *test_info, const getdns_dict *response, + const char *section, getdns_list **answers, size_t *no_answers) { getdns_return_t ret; + char buf[40]; - if ((ret = getdns_dict_get_list(response, "/replies_tree/0/answer", answers)) != GETDNS_RETURN_GOOD) { + snprintf(buf, sizeof(buf), "/replies_tree/0/%s", section); + + if ((ret = getdns_dict_get_list(response, buf, answers)) != GETDNS_RETURN_GOOD) { fprintf(test_info->errout, - "Cannot get answers: %s (%d)", + "Cannot get section '%s': %s (%d)", + section, getdns_get_errorstr_by_id(ret), ret); return EXIT_UNKNOWN; @@ -486,13 +497,16 @@ static exit_value_t get_answers(const struct test_info_s *test_info, if ((ret = getdns_list_get_length(*answers, no_answers)) != GETDNS_RETURN_GOOD) { fprintf(test_info->errout, - "Cannot get number of answers: %s (%d)", + "Cannot get number of items in '%s': %s (%d)", + section, getdns_get_errorstr_by_id(ret), ret); return EXIT_UNKNOWN; } if (*no_answers <= 0) { - fputs("Got zero answers", test_info->errout); + fprintf(test_info->errout, + "Zero entries in '%s'", + section); return EXIT_WARNING; } @@ -507,7 +521,7 @@ static exit_value_t check_answer_type(const struct test_info_s *test_info, size_t no_answers; exit_value_t xit; - if ((xit = get_answers(test_info, response, &answers, &no_answers)) != EXIT_OK) + if ((xit = get_answers(test_info, response, "answer", &answers, &no_answers)) != EXIT_OK) return xit; for (size_t i = 0; i < no_answers; ++i) { @@ -744,7 +758,7 @@ static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, getdns_list *answers; size_t no_answers; - if ((xit = get_answers(test_info, response, &answers, &no_answers)) != EXIT_OK) + if ((xit = get_answers(test_info, response, "answer", &answers, &no_answers)) != EXIT_OK) return xit; for (size_t i = 0; i < no_answers; ++i) { @@ -773,6 +787,7 @@ static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, continue; getdns_bindata *rtxt; + if ((ret = getdns_dict_get_bindata(answer, "/rdata/txt_strings/0", &rtxt)) != GETDNS_RETURN_GOOD) { fputs("No answer text", test_info->errout); return EXIT_WARNING; @@ -799,6 +814,130 @@ static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, return EXIT_UNKNOWN; } +static exit_value_t test_padding(const struct test_info_s *test_info, + char ** av) +{ + getdns_dict *response; + exit_value_t xit; + long blocksize; + char *endptr; + const char USAGE[] = "padding takes arguments [ []]"; + + if (!*av || (blocksize = strtol(*av, &endptr, 10), *endptr != '\0' || blocksize < 0)) { + fputs(USAGE, test_info->errout); + return EXIT_USAGE; + } + ++av; + + getdns_return_t ret; + if ((ret = getdns_context_set_tls_query_padding_blocksize(test_info->context, (uint16_t) blocksize)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot set padding blocksize: %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 *answers; + size_t no_answers; + + if ((xit = get_answers(test_info, response, "additional", &answers, &no_answers)) != EXIT_OK) + return xit; + + for (size_t i = 0; i < no_answers; ++i) { + getdns_dict *answer; + getdns_return_t ret; + + if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get answer number %zu: %s (%d)", + i, + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + uint32_t rtype; + + if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get answer type: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + if (rtype != GETDNS_RRTYPE_OPT) + continue; + + getdns_list *options; + size_t no_options; + + if ((ret = getdns_dict_get_list(answer, "/rdata/options", &options)) != GETDNS_RETURN_GOOD) { + goto no_padding; + } + if ((ret = getdns_list_get_length(options, &no_options)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get number of options: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + for (size_t j = 0; j < no_options; ++j) { + getdns_dict *option; + uint32_t code; + + if ((ret = getdns_list_get_dict(options, j, &option)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get option number %zu: %s (%d)", + j, + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + if ((ret = getdns_dict_get_int(option, "option_code", &code)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get option code: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + if (code != EDNS0_PADDING_CODE) + continue; + + /* Yes, we have padding! */ + getdns_bindata *data; + + if ((ret = getdns_dict_get_bindata(option, "option_data", &data)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get option code: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + fprintf(test_info->errout, + "Padding found, length %zu", + data->size); + return EXIT_OK; + } + } + +no_padding: + fputs("No padding found", test_info->errout); + return EXIT_CRITICAL; +} + static struct test_funcs_s { const char *name; @@ -811,6 +950,7 @@ static struct test_funcs_s { "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 } }; From 3666d994a73d8ca9c12ddf6af708378b64abecd6 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 16 Jan 2018 15:08:22 +0000 Subject: [PATCH 21/47] 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", From 6bd0f8b98002151b941639c6acf13d204c29f3ce Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 16 Jan 2018 18:09:35 +0000 Subject: [PATCH 22/47] Encode exit status words in () to make it clear that it's not part of the sentence. 'Server validates OK' -> 'Server validates (OK)' --- src/tools/getdns_server_mon.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 53e06b7b..cc46851a 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -1284,19 +1284,19 @@ int main(int ac, char *av[]) exit_value_t xit = f->func(&test_info, av); switch(xit) { case EXIT_OK: - fputs(" OK", test_info.errout); + fputs(" (OK)", test_info.errout); break; case EXIT_WARNING: - fputs(" WARNING", test_info.errout); + fputs(" (WARNING)", test_info.errout); break; case EXIT_CRITICAL: - fputs(" CRITICAL", test_info.errout); + fputs(" (CRITICAL)", test_info.errout); break; case EXIT_UNKNOWN: - fputs(" UNKNOWN", test_info.errout); + fputs(" (UNKNOWN)", test_info.errout); break; case EXIT_USAGE: @@ -1304,7 +1304,7 @@ int main(int ac, char *av[]) break; default: - fputs(" ???", test_info.errout); + fputs(" (\?\?\?)", test_info.errout); break; } fputc('\n', test_info.errout); From 760269acbd243d7653913c4f24bede1ccdb5fa93 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Wed, 17 Jan 2018 15:35:56 +0000 Subject: [PATCH 23/47] Make internal types POSIX-compliant by not naming them *_t. See: http://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xsh_chap02.html#tag_22_02_12_01 The change tacitly ignores the colossal number of coach and horses the entire world, including getdns, has stampeded through this POSIX hope for decades, but simply hopes for some small recognition when the Recording Angel tots up the damages. --- src/tools/getdns_server_mon.c | 60 +++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index cc46851a..9751b52c 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -63,7 +63,7 @@ typedef enum { EXIT_CRITICAL, EXIT_UNKNOWN, EXIT_USAGE /* Special case - internal only. */ -} exit_value_t; +} exit_value; /* Plugin verbosity values */ typedef enum { @@ -71,7 +71,7 @@ typedef enum { VERBOSITY_ADDITIONAL, VERBOSITY_CONFIG, VERBOSITY_DEBUG -} verbosity_t; +} plugin_verbosity; static struct test_info_s { @@ -80,7 +80,7 @@ static struct test_info_s /* Output control */ bool monitoring; FILE *errout; - verbosity_t verbosity; + plugin_verbosity verbosity; /* Test config info */ bool fail_on_dns_errors; @@ -260,7 +260,7 @@ static void get_thresholds(char ***av, return; } -static exit_value_t get_name_type_args(const struct test_info_s *test_info, +static exit_value get_name_type_args(const struct test_info_s *test_info, char ***av, const char **lookup_name, uint32_t *lookup_type) @@ -286,7 +286,7 @@ static exit_value_t get_name_type_args(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value_t search(const struct test_info_s *test_info, +static exit_value search(const struct test_info_s *test_info, const char *name, uint16_t type, getdns_dict **response) @@ -333,7 +333,7 @@ static exit_value_t search(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value_t check_result(const struct test_info_s *test_info, +static exit_value check_result(const struct test_info_s *test_info, const getdns_dict *response) { getdns_return_t ret; @@ -383,7 +383,7 @@ static exit_value_t check_result(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value_t get_report_info(const struct test_info_s *test_info, +static exit_value get_report_info(const struct test_info_s *test_info, const getdns_dict *response, uint32_t *rtt, getdns_bindata **auth_status, @@ -478,7 +478,7 @@ static exit_value_t get_report_info(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value_t get_answers(const struct test_info_s *test_info, +static exit_value get_answers(const struct test_info_s *test_info, const getdns_dict *response, const char *section, getdns_list **answers, @@ -516,13 +516,13 @@ static exit_value_t get_answers(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value_t check_answer_type(const struct test_info_s *test_info, +static exit_value check_answer_type(const struct test_info_s *test_info, const getdns_dict *response, uint32_t rrtype) { getdns_list *answers; size_t no_answers; - exit_value_t xit; + exit_value xit; if ((xit = get_answers(test_info, response, "answer", &answers, &no_answers)) != EXIT_OK) return xit; @@ -557,7 +557,7 @@ static exit_value_t check_answer_type(const struct test_info_s *test_info, return EXIT_UNKNOWN; } -static exit_value_t search_check(const struct test_info_s *test_info, +static exit_value search_check(const struct test_info_s *test_info, const char *lookup_name, uint16_t lookup_type, getdns_dict **response, @@ -565,7 +565,7 @@ static exit_value_t search_check(const struct test_info_s *test_info, getdns_bindata **auth_status, time_t *cert_expire_time) { - exit_value_t xit; + exit_value xit; getdns_dict *resp; if ((xit = search(test_info, lookup_name, lookup_type, &resp)) != EXIT_OK) @@ -585,7 +585,7 @@ static exit_value_t search_check(const struct test_info_s *test_info, return xit; } -static exit_value_t parse_search_check(const struct test_info_s *test_info, +static exit_value parse_search_check(const struct test_info_s *test_info, char **av, const char *usage, getdns_dict **response, @@ -595,7 +595,7 @@ static exit_value_t parse_search_check(const struct test_info_s *test_info, { const char *lookup_name = DEFAULT_LOOKUP_NAME; uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; - exit_value_t xit; + exit_value xit; if ((xit = get_name_type_args(test_info, &av, &lookup_name, &lookup_type)) != EXIT_OK) return xit; @@ -615,10 +615,10 @@ static exit_value_t parse_search_check(const struct test_info_s *test_info, ** Test routines. **/ -static exit_value_t test_lookup(const struct test_info_s *test_info, +static exit_value test_lookup(const struct test_info_s *test_info, char ** av) { - exit_value_t xit; + exit_value xit; if ((xit = parse_search_check(test_info, av, @@ -633,10 +633,10 @@ static exit_value_t test_lookup(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value_t test_rtt(const struct test_info_s *test_info, +static exit_value test_rtt(const struct test_info_s *test_info, char ** av) { - exit_value_t xit; + exit_value xit; int critical_ms = RTT_CRITICAL_MS; int warning_ms = RTT_WARNING_MS; uint32_t rtt_val; @@ -661,10 +661,10 @@ static exit_value_t test_rtt(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value_t test_authenticate(const struct test_info_s *test_info, +static exit_value test_authenticate(const struct test_info_s *test_info, char ** av) { - exit_value_t xit; + exit_value xit; getdns_bindata *auth_status; if ((xit = parse_search_check(test_info, @@ -685,10 +685,10 @@ static exit_value_t test_authenticate(const struct test_info_s *test_info, } } -static exit_value_t test_certificate_valid(const struct test_info_s *test_info, +static exit_value test_certificate_valid(const struct test_info_s *test_info, char **av) { - exit_value_t xit; + exit_value xit; int warning_days = CERT_EXPIRY_WARNING_DAYS; int critical_days = CERT_EXPIRY_CRITICAL_DAYS; time_t expire_time; @@ -737,7 +737,7 @@ static exit_value_t test_certificate_valid(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, +static exit_value test_qname_minimisation(const struct test_info_s *test_info, char ** av) { if (*av) { @@ -747,7 +747,7 @@ static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, } getdns_dict *response; - exit_value_t xit; + exit_value xit; if ((xit = search_check(test_info, "qnamemintest.internet.nl", @@ -817,11 +817,11 @@ static exit_value_t test_qname_minimisation(const struct test_info_s *test_info, return EXIT_UNKNOWN; } -static exit_value_t test_padding(const struct test_info_s *test_info, +static exit_value test_padding(const struct test_info_s *test_info, char ** av) { getdns_dict *response; - exit_value_t xit; + exit_value xit; long blocksize; char *endptr; const char USAGE[] = "padding takes arguments [ []]"; @@ -941,11 +941,11 @@ no_padding: return EXIT_CRITICAL; } -static exit_value_t test_keepalive(const struct test_info_s *test_info, +static exit_value test_keepalive(const struct test_info_s *test_info, char ** av) { getdns_dict *response; - exit_value_t xit; + exit_value xit; long long timeout; char *endptr; const char USAGE[] = "keepalive takes arguments [ []]"; @@ -1033,7 +1033,7 @@ 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); + exit_value (*func)(const struct test_info_s *test_info, char **av); } TESTS[] = { { "lookup", false, false, test_lookup }, @@ -1281,7 +1281,7 @@ int main(int ac, char *av[]) } } - exit_value_t xit = f->func(&test_info, av); + exit_value xit = f->func(&test_info, av); switch(xit) { case EXIT_OK: fputs(" (OK)", test_info.errout); From 00c17dca14ad4beb5ffb69430b74764daf6f5b6c Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Wed, 17 Jan 2018 18:38:28 +0000 Subject: [PATCH 24/47] Add to certificate time conversion to cope with pre-1.0.2 OpenSSL. Also tag printed time with UTC. The time parse with pre-1.0.2 is a best effort, and relies on timegm() to convert struct tm in UTC to time_t. There being attractive alternative. Isn't C time handling grotty? --- src/tools/getdns_server_mon.c | 61 ++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 9751b52c..a1dd05a7 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -139,6 +139,64 @@ static bool extract_cert_expiry(const unsigned char *data, size_t len, time_t *t ASN1_TIME *not_after = X509_get_notAfter(cert); +#if OPENSSL_VERSION_NUMBER < 0x10002000 + /* + * OpenSSL before 1.0.2 does not support ASN1_TIME_diff(). + * So work around by using ASN1_TIME_print() to print to a buffer + * and parsing that. This does not do any kind of sane format, + * but 'Mar 15 11:58:50 2018 GMT'. Note the month name is not + * locale-dependent but always English, so strptime() to parse + * isn't going to work. It also *appears* to always end 'GMT'. + * timegm() is a glibc-ism, but there is no sensible + * alternative. Patches welcome... + */ + + char buf[40]; + BIO *b = BIO_new(BIO_s_mem()); + if (ASN1_TIME_print(b, not_after) <= 0) { + BIO_free(b); + X509_free(cert); + return false; + } + if (BIO_gets(b, buf, sizeof(buf)) <= 0) { + BIO_free(b); + X509_free(cert); + return false; + } + BIO_free(b); + X509_free(cert); + + struct tm tm; + char month[4]; + char tz[4]; + memset(&tm, 0, sizeof(tm)); + if (sscanf(buf, + "%3s %d %d:%d:%d %d %3s", + month, + &tm.tm_mday, + &tm.tm_hour, + &tm.tm_min, + &tm.tm_sec, + &tm.tm_year, + tz) != 7) + return false; + tm.tm_year -= 1900; + if (strcmp(tz, "GMT") != 0) + return false; + + const char *mon[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + while(tm.tm_mon < 12 && strcmp(mon[tm.tm_mon], month) != 0) + ++tm.tm_mon; + if (tm.tm_mon > 11) + return false; + + *t = timegm(&tm); + return true; +#else /* * Use ASN1_TIME_diff to get a time delta between now and expiry. * This is much easier than trying to parse the time. @@ -150,6 +208,7 @@ static bool extract_cert_expiry(const unsigned char *data, size_t len, time_t *t X509_free(cert); return true; +#endif } static void exit_tidy() @@ -468,7 +527,7 @@ static exit_value get_report_info(const struct test_info_s *test_info, if (test_info->verbosity >= VERBOSITY_ADDITIONAL) { struct tm *tm = gmtime(&cert_expire_time_val); char buf[25]; - strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm); + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC", tm); fprintf(test_info->errout, "cert expiry %s, ", buf); } } From add818fea203d5c55684cd50e495297fc4df6ac3 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Thu, 18 Jan 2018 10:55:44 +0000 Subject: [PATCH 25/47] Remove dependency on timegm() when using OpenSSL < 1.0.2. Convert dates to Julian and diff. This is basically what ASN1_TIME_diff() does internally. And that's quite enough near-pointless polishing here. --- src/tools/getdns_server_mon.c | 57 ++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index a1dd05a7..744a862c 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -128,7 +128,33 @@ static const char *rcode_text(int rcode) return text[rcode]; } -/* Thanks to: +#if OPENSSL_VERSION_NUMBER < 0x10002000 +/* + * Convert date to Julian day. + * See https://en.wikipedia.org/wiki/Julian_day + */ +static long julian_day(const struct tm *tm) +{ + long dd, mm, yyyy; + + dd = tm->tm_mday; + mm = tm->tm_mon + 1; + yyyy = tm->tm_year + 1900; + + return (1461 * (yyyy + 4800 + (mm - 14) / 12)) / 4 + + (367 * (mm - 2 - 12 * ((mm - 14) / 12))) / 12 - + (3 * ((yyyy + 4900 + (mm - 14) / 12) / 100)) / 4 + + dd - 32075; +} + +static long secs_in_day(const struct tm *tm) +{ + return ((tm->tm_hour * 60) + tm->tm_min) * 60 + tm->tm_sec; +} +#endif + +/* + * Thanks to: * https://zakird.com/2013/10/13/certificate-parsing-with-openssl */ static bool extract_cert_expiry(const unsigned char *data, size_t len, time_t *t) @@ -137,8 +163,12 @@ static bool extract_cert_expiry(const unsigned char *data, size_t len, time_t *t if (!cert) return false; + int day_diff, sec_diff; + const long SECS_IN_DAY = 60 * 60 * 24; ASN1_TIME *not_after = X509_get_notAfter(cert); + *t = time(NULL); + #if OPENSSL_VERSION_NUMBER < 0x10002000 /* * OpenSSL before 1.0.2 does not support ASN1_TIME_diff(). @@ -147,8 +177,9 @@ static bool extract_cert_expiry(const unsigned char *data, size_t len, time_t *t * but 'Mar 15 11:58:50 2018 GMT'. Note the month name is not * locale-dependent but always English, so strptime() to parse * isn't going to work. It also *appears* to always end 'GMT'. - * timegm() is a glibc-ism, but there is no sensible - * alternative. Patches welcome... + * Ideally one could then convert this UTC time to a time_t, but + * there's no way to do that in standard C/POSIX. So follow the + * lead of OpenSSL, convert to Julian days and use the difference. */ char buf[40]; @@ -194,21 +225,25 @@ static bool extract_cert_expiry(const unsigned char *data, size_t len, time_t *t if (tm.tm_mon > 11) return false; - *t = timegm(&tm); - return true; + struct tm tm_now; + gmtime_r(t, &tm_now); + + day_diff = julian_day(&tm) - julian_day(&tm_now); + sec_diff = secs_in_day(&tm) - secs_in_day(&tm_now); + if (sec_diff < 0) { + sec_diff += SECS_IN_DAY; + --day_diff; + } #else /* * Use ASN1_TIME_diff to get a time delta between now and expiry. * This is much easier than trying to parse the time. */ - int day, sec; - *t = time(NULL); - ASN1_TIME_diff(&day, &sec, NULL, not_after); - *t += day * 86400 + sec; - + ASN1_TIME_diff(&day_diff, &sec_diff, NULL, not_after); X509_free(cert); - return true; #endif + *t += day_diff * SECS_IN_DAY + sec_diff; + return true; } static void exit_tidy() From f5322c701daf6f30a48f3f7e7270dceeb1a20fc9 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Thu, 18 Jan 2018 11:48:56 +0000 Subject: [PATCH 26/47] Add more missing make targets causing test 105 to fail. It's amazing how fiddly it is to add a single executable/source file to the build. --- Makefile.in | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile.in b/Makefile.in index d126a0ba..f6d7acab 100644 --- a/Makefile.in +++ b/Makefile.in @@ -128,6 +128,12 @@ install-getdns_query: install-lib uninstall-getdns_query: cd src/tools && $(MAKE) $@ +install-getdns_server_mon: install-lib + cd src/tools && $(MAKE) $@ + +uninstall-getdns_server_mon: + cd src/tools && $(MAKE) $@ + install-stubby: cd src && $(MAKE) $@ From ea035fa82e1be4b470b344c23537bd406f68f926 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Thu, 18 Jan 2018 17:16:28 +0000 Subject: [PATCH 27/47] Correct some code formatting. --- src/tools/getdns_server_mon.c | 82 +++++++++++++++++------------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 744a862c..8ebfd2dd 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -355,9 +355,9 @@ static void get_thresholds(char ***av, } static exit_value get_name_type_args(const struct test_info_s *test_info, - char ***av, - const char **lookup_name, - uint32_t *lookup_type) + char ***av, + const char **lookup_name, + uint32_t *lookup_type) { if (**av) { if (strlen(**av) > 0) { @@ -381,9 +381,9 @@ static exit_value get_name_type_args(const struct test_info_s *test_info, } static exit_value search(const struct test_info_s *test_info, - const char *name, - uint16_t type, - getdns_dict **response) + const char *name, + uint16_t type, + getdns_dict **response) { getdns_return_t ret; getdns_dict *extensions = getdns_dict_create(); @@ -428,7 +428,7 @@ static exit_value search(const struct test_info_s *test_info, } static exit_value check_result(const struct test_info_s *test_info, - const getdns_dict *response) + const getdns_dict *response) { getdns_return_t ret; uint32_t error_id; @@ -478,10 +478,10 @@ static exit_value check_result(const struct test_info_s *test_info, } static exit_value get_report_info(const struct test_info_s *test_info, - const getdns_dict *response, - uint32_t *rtt, - getdns_bindata **auth_status, - time_t *cert_expire_time) + const getdns_dict *response, + uint32_t *rtt, + getdns_bindata **auth_status, + time_t *cert_expire_time) { getdns_return_t ret; getdns_list *l; @@ -573,10 +573,10 @@ static exit_value get_report_info(const struct test_info_s *test_info, } static exit_value get_answers(const struct test_info_s *test_info, - const getdns_dict *response, - const char *section, - getdns_list **answers, - size_t *no_answers) + const getdns_dict *response, + const char *section, + getdns_list **answers, + size_t *no_answers) { getdns_return_t ret; char buf[40]; @@ -611,8 +611,8 @@ static exit_value get_answers(const struct test_info_s *test_info, } static exit_value check_answer_type(const struct test_info_s *test_info, - const getdns_dict *response, - uint32_t rrtype) + const getdns_dict *response, + uint32_t rrtype) { getdns_list *answers; size_t no_answers; @@ -652,12 +652,12 @@ static exit_value check_answer_type(const struct test_info_s *test_info, } static exit_value search_check(const struct test_info_s *test_info, - const char *lookup_name, - uint16_t lookup_type, - getdns_dict **response, - uint32_t *rtt, - getdns_bindata **auth_status, - time_t *cert_expire_time) + const char *lookup_name, + uint16_t lookup_type, + getdns_dict **response, + uint32_t *rtt, + getdns_bindata **auth_status, + time_t *cert_expire_time) { exit_value xit; getdns_dict *resp; @@ -680,12 +680,12 @@ static exit_value search_check(const struct test_info_s *test_info, } static exit_value parse_search_check(const struct test_info_s *test_info, - char **av, - const char *usage, - getdns_dict **response, - uint32_t *rtt, - getdns_bindata **auth_status, - time_t *cert_expire_time) + char **av, + const char *usage, + getdns_dict **response, + uint32_t *rtt, + getdns_bindata **auth_status, + time_t *cert_expire_time) { const char *lookup_name = DEFAULT_LOOKUP_NAME; uint32_t lookup_type = DEFAULT_LOOKUP_TYPE; @@ -710,7 +710,7 @@ static exit_value parse_search_check(const struct test_info_s *test_info, **/ static exit_value test_lookup(const struct test_info_s *test_info, - char ** av) + char ** av) { exit_value xit; @@ -728,7 +728,7 @@ static exit_value test_lookup(const struct test_info_s *test_info, } static exit_value test_rtt(const struct test_info_s *test_info, - char ** av) + char ** av) { exit_value xit; int critical_ms = RTT_CRITICAL_MS; @@ -756,7 +756,7 @@ static exit_value test_rtt(const struct test_info_s *test_info, } static exit_value test_authenticate(const struct test_info_s *test_info, - char ** av) + char ** av) { exit_value xit; getdns_bindata *auth_status; @@ -780,7 +780,7 @@ static exit_value test_authenticate(const struct test_info_s *test_info, } static exit_value test_certificate_valid(const struct test_info_s *test_info, - char **av) + char **av) { exit_value xit; int warning_days = CERT_EXPIRY_WARNING_DAYS; @@ -832,7 +832,7 @@ static exit_value test_certificate_valid(const struct test_info_s *test_info, } static exit_value test_qname_minimisation(const struct test_info_s *test_info, - char ** av) + char ** av) { if (*av) { fputs("qname-min takes no arguments", @@ -912,7 +912,7 @@ static exit_value test_qname_minimisation(const struct test_info_s *test_info, } static exit_value test_padding(const struct test_info_s *test_info, - char ** av) + char ** av) { getdns_dict *response; exit_value xit; @@ -1036,7 +1036,7 @@ no_padding: } static exit_value test_keepalive(const struct test_info_s *test_info, - char ** av) + char ** av) { getdns_dict *response; exit_value xit; @@ -1103,11 +1103,11 @@ static exit_value test_keepalive(const struct test_info_s *test_info, 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; + fprintf(test_info->errout, + "Cannot get idle timeout: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; } if (overflow) { From 1a3025a40574cfc821b38cef3de7157958be33a8 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Thu, 18 Jan 2018 17:17:16 +0000 Subject: [PATCH 28/47] If server does not return expected TXT in qname-min, return UNKNOWN not WARNING. --- src/tools/getdns_server_mon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 8ebfd2dd..8b177541 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -887,7 +887,7 @@ static exit_value test_qname_minimisation(const struct test_info_s *test_info, if ((ret = getdns_dict_get_bindata(answer, "/rdata/txt_strings/0", &rtxt)) != GETDNS_RETURN_GOOD) { fputs("No answer text", test_info->errout); - return EXIT_WARNING; + return EXIT_UNKNOWN; } if (rtxt->size > 0 ) { From 3fd4f7f240f907765398edcf6ff4f8d32a1bee84 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 16 Jan 2018 18:06:00 +0000 Subject: [PATCH 29/47] Add 'dnssec-validate' test. This test checks whether the server does DNSSEC validation. If it manages to find an A record for dnssec-failed.org, it doesn't. --- src/tools/getdns_server_mon.c | 113 ++++++++++++++++++++++++++++------ 1 file changed, 93 insertions(+), 20 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 8b177541..6f4523ec 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -284,6 +284,8 @@ static void usage() " rtt [warn-ms,crit-ms] [ []]\n" " Check server round trip time (default 500,250)\n" "\n" +" dnssec-validate Check whether server does DNSSEC validation\n" +"\n" " tls-auth [ []] Check authentication of TLS server\n" " If both a SPKI pin and authentication name are\n" " provided, both must authenticate for this test\n" @@ -427,13 +429,14 @@ static exit_value search(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value check_result(const struct test_info_s *test_info, - const getdns_dict *response) +static exit_value get_result(const struct test_info_s *test_info, + const getdns_dict *response, + uint32_t *error_id, + uint32_t *rcode) { getdns_return_t ret; - uint32_t error_id; - if ((ret = getdns_dict_get_int(response, "status", &error_id)) != GETDNS_RETURN_GOOD) { + if ((ret = getdns_dict_get_int(response, "status", error_id)) != GETDNS_RETURN_GOOD) { fprintf(test_info->errout, "Cannot get result status: %s (%d)", getdns_get_errorstr_by_id(ret), @@ -441,24 +444,12 @@ static exit_value check_result(const struct test_info_s *test_info, return EXIT_UNKNOWN; } - if (test_info->verbosity >= VERBOSITY_ADDITIONAL){ - fprintf(test_info->errout, - "result: %s (%d), ", - getdns_get_errorstr_by_id(error_id), - error_id); + if (*error_id != GETDNS_RESPSTATUS_GOOD && *error_id != GETDNS_RESPSTATUS_NO_NAME) { + *rcode = 0; + return EXIT_OK; } - if (error_id == GETDNS_RESPSTATUS_GOOD) - return EXIT_OK; - - uint32_t rcode; - - ret = getdns_dict_get_int(response, "/replies_tree/0/header/rcode", &rcode); - if (ret == GETDNS_RETURN_NO_SUCH_DICT_NAME || - ret == GETDNS_RETURN_NO_SUCH_LIST_ITEM) { - fputs("Search had no results, timeout?", test_info->errout); - return EXIT_CRITICAL; - } else if (ret != GETDNS_RETURN_GOOD) { + if ((ret = getdns_dict_get_int(response, "/replies_tree/0/header/rcode", rcode)) != GETDNS_RETURN_GOOD) { fprintf(test_info->errout, "Cannot get DNS return code: %s (%d)", getdns_get_errorstr_by_id(ret), @@ -466,6 +457,42 @@ static exit_value check_result(const struct test_info_s *test_info, return EXIT_UNKNOWN; } + return EXIT_OK; +} + +static exit_value check_result(const struct test_info_s *test_info, + const getdns_dict *response) +{ + exit_value xit; + uint32_t error_id, rcode; + + if ((xit = get_result(test_info, response, &error_id, &rcode)) != EXIT_OK) + return xit; + + switch(error_id) { + case GETDNS_RESPSTATUS_ALL_TIMEOUT: + fputs("Search timed out", test_info->errout); + return EXIT_CRITICAL; + + case GETDNS_RESPSTATUS_NO_SECURE_ANSWERS: + fputs("No secure answers", test_info->errout); + return EXIT_CRITICAL; + + case GETDNS_RESPSTATUS_ALL_BOGUS_ANSWERS: + fputs("All answers are bogus", test_info->errout); + return EXIT_CRITICAL; + + default: + break; + } + + if (test_info->verbosity >= VERBOSITY_ADDITIONAL){ + fprintf(test_info->errout, + "result: %s (%d), ", + getdns_get_errorstr_by_id(error_id), + error_id); + } + if (test_info->fail_on_dns_errors && rcode > 0) { fprintf(test_info->errout, "DNS error %s (%d)", @@ -1122,6 +1149,51 @@ static exit_value test_keepalive(const struct test_info_s *test_info, } } +static exit_value test_dnssec_validate(const struct test_info_s *test_info, + char ** av) +{ + if (*av) { + fputs("dnssec-validate takes no arguments", + test_info->errout); + return EXIT_USAGE; + } + + getdns_dict *response; + exit_value xit; + + if ((xit = search(test_info, + "dnssec-failed.org", + GETDNS_RRTYPE_A, + &response)) != EXIT_OK) + return xit; + + if ((xit = get_report_info(test_info, response, NULL, NULL, NULL)) != EXIT_OK) + return xit; + + uint32_t error_id, rcode; + + if ((xit = get_result(test_info, response, &error_id, &rcode)) != EXIT_OK) + return xit; + + switch(error_id) { + case GETDNS_RESPSTATUS_ALL_TIMEOUT: + fputs("Search timed out", test_info->errout); + return EXIT_CRITICAL; + + case GETDNS_RESPSTATUS_NO_SECURE_ANSWERS: + case GETDNS_RESPSTATUS_ALL_BOGUS_ANSWERS: + case GETDNS_RESPSTATUS_NO_NAME: + fputs("Server validates DNSSEC", test_info->errout); + return EXIT_OK; + + default: + break; + } + + fputs("Server does NOT validate DNSSEC", test_info->errout); + return EXIT_CRITICAL; +} + static struct test_funcs_s { const char *name; @@ -1137,6 +1209,7 @@ static struct test_funcs_s { "tls-cert-valid", true, false, test_certificate_valid }, { "tls-padding", true, false, test_padding }, { "keepalive", false, true, test_keepalive }, + { "dnssec-validate", false, true, test_dnssec_validate }, { NULL, false, false, NULL } }; From 62ad159f152451eca405539f295eb0dce8a79c6d Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Wed, 17 Jan 2018 13:01:29 +0000 Subject: [PATCH 30/47] Update dnssec-validate. Check we can retrieve info for bogus domain, and remove must use TCP flag. Run a second query with the CD bit set and check that succeeds. --- src/tools/getdns_server_mon.c | 91 ++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 23 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 6f4523ec..c3dcad9b 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -385,17 +385,21 @@ static exit_value get_name_type_args(const struct test_info_s *test_info, static exit_value search(const struct test_info_s *test_info, const char *name, uint16_t type, + getdns_dict *extensions, getdns_dict **response) { getdns_return_t ret; - getdns_dict *extensions = getdns_dict_create(); + getdns_dict *search_extensions = + (extensions) ? extensions : getdns_dict_create(); - if ((ret = getdns_dict_set_int(extensions, "return_call_reporting", GETDNS_EXTENSION_TRUE)) != GETDNS_RETURN_GOOD) { + /* We always turn on the return_call_reporting extension. */ + if ((ret = getdns_dict_set_int(search_extensions, "return_call_reporting", GETDNS_EXTENSION_TRUE)) != GETDNS_RETURN_GOOD) { fprintf(test_info->errout, "Cannot set return call reporting: %s (%d)", getdns_get_errorstr_by_id(ret), ret); - getdns_dict_destroy(extensions); + if (!extensions) + getdns_dict_destroy(search_extensions); return EXIT_UNKNOWN; } @@ -408,9 +412,10 @@ static exit_value search(const struct test_info_s *test_info, ret = getdns_general_sync(test_info->context, name, type, - extensions, + search_extensions, response); - getdns_dict_destroy(extensions); + if (!extensions) + getdns_dict_destroy(search_extensions); if (ret != GETDNS_RETURN_GOOD) { fprintf(test_info->errout, "Error resolving '%s': %s (%d)", @@ -689,7 +694,7 @@ static exit_value search_check(const struct test_info_s *test_info, exit_value xit; getdns_dict *resp; - if ((xit = search(test_info, lookup_name, lookup_type, &resp)) != EXIT_OK) + if ((xit = search(test_info, lookup_name, lookup_type, NULL, &resp)) != EXIT_OK) return xit; if ((xit = check_result(test_info, resp)) != EXIT_OK) @@ -1164,34 +1169,73 @@ static exit_value test_dnssec_validate(const struct test_info_s *test_info, if ((xit = search(test_info, "dnssec-failed.org", GETDNS_RRTYPE_A, + NULL, &response)) != EXIT_OK) return xit; - if ((xit = get_report_info(test_info, response, NULL, NULL, NULL)) != EXIT_OK) - return xit; - uint32_t error_id, rcode; if ((xit = get_result(test_info, response, &error_id, &rcode)) != EXIT_OK) return xit; - switch(error_id) { - case GETDNS_RESPSTATUS_ALL_TIMEOUT: + if (error_id == GETDNS_RESPSTATUS_ALL_TIMEOUT) { fputs("Search timed out", test_info->errout); return EXIT_CRITICAL; - - case GETDNS_RESPSTATUS_NO_SECURE_ANSWERS: - case GETDNS_RESPSTATUS_ALL_BOGUS_ANSWERS: - case GETDNS_RESPSTATUS_NO_NAME: - fputs("Server validates DNSSEC", test_info->errout); - return EXIT_OK; - - default: - break; } - fputs("Server does NOT validate DNSSEC", test_info->errout); - return EXIT_CRITICAL; + if (rcode != GETDNS_RCODE_SERVFAIL) { + fputs("Server does NOT validate DNSSEC", test_info->errout); + return EXIT_CRITICAL; + } + + /* + * Rerun the query, but this time set the CD bit. The lookup should + * succeed. + */ + getdns_return_t ret; + getdns_dict *response2; + getdns_dict *extensions = getdns_dict_create(); + + if ((ret = getdns_dict_set_int(extensions, "/header/cd", 1)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot set CD bit: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + getdns_dict_destroy(extensions); + return EXIT_UNKNOWN; + } + + if ((xit = search(test_info, + "dnssec-failed.org", + GETDNS_RRTYPE_A, + extensions, + &response2)) != EXIT_OK) + return xit; + + getdns_dict_destroy(extensions); + + /* + * Only now get report info from the first search, so that any + * verbose output appears after the context/reponse dumps. + */ + if ((xit = get_report_info(test_info, response, NULL, NULL, NULL)) != EXIT_OK) + return xit; + + if ((xit = get_result(test_info, response2, &error_id, &rcode)) != EXIT_OK) + return xit; + + if (error_id == GETDNS_RESPSTATUS_ALL_TIMEOUT) { + fputs("Search timed out", test_info->errout); + return EXIT_CRITICAL; + } + + if (error_id != GETDNS_RESPSTATUS_GOOD || rcode != GETDNS_RCODE_NOERROR) { + fputs("Server error - cannot determine DNSSEC status", test_info->errout); + return EXIT_UNKNOWN; + } + + fputs("Server validates DNSSEC", test_info->errout); + return EXIT_OK; } static struct test_funcs_s @@ -1209,7 +1253,7 @@ static struct test_funcs_s { "tls-cert-valid", true, false, test_certificate_valid }, { "tls-padding", true, false, test_padding }, { "keepalive", false, true, test_keepalive }, - { "dnssec-validate", false, true, test_dnssec_validate }, + { "dnssec-validate", false, false, test_dnssec_validate }, { NULL, false, false, NULL } }; @@ -1471,6 +1515,7 @@ int main(int ac, char *av[]) break; default: + /* ??? is a trigraph... */ fputs(" (\?\?\?)", test_info.errout); break; } From 0291e205fdf5de997824e98005798a9f15ad964a Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Thu, 18 Jan 2018 13:59:46 +0000 Subject: [PATCH 31/47] Add TLS 1.3 test. Add a new item tls_version to call_reporting, containing the OpenSSL version string for the name of the protocol used for the connection. The test does a normal lookup, but first sets the cipher list to TLS1.3 only ciphers. This will cause a Bad Context error at search time, so we can tell if the underlying OpenSSL library lacks TLS 1.3. The check the call reporting for a TLS version of "TLSv1.3". --- src/request-internal.c | 1 + src/stub.c | 14 ++-- src/tools/getdns_server_mon.c | 132 +++++++++++++++++++++++++++++++--- src/types-internal.h | 1 + src/util-internal.c | 6 ++ 5 files changed, 139 insertions(+), 15 deletions(-) diff --git a/src/request-internal.c b/src/request-internal.c index cc082039..8b5dafdb 100644 --- a/src/request-internal.c +++ b/src/request-internal.c @@ -211,6 +211,7 @@ network_req_init(getdns_network_req *net_req, getdns_dns_req *owner, net_req->debug_tls_auth_status = GETDNS_AUTH_NONE; net_req->debug_tls_peer_cert.size = 0; net_req->debug_tls_peer_cert.data = NULL; + net_req->debug_tls_version = NULL; net_req->debug_udp = 0; /* Scheduling, touch only via _getdns_netreq_change_state! diff --git a/src/stub.c b/src/stub.c index 7879e0c7..ad4708c7 100644 --- a/src/stub.c +++ b/src/stub.c @@ -1687,12 +1687,14 @@ upstream_write_cb(void *userarg) remove_from_write_queue(upstream, netreq); if (netreq->owner->return_call_reporting && - netreq->upstream->tls_obj && - netreq->debug_tls_peer_cert.data == NULL && - (cert = SSL_get_peer_certificate(netreq->upstream->tls_obj))) { - netreq->debug_tls_peer_cert.size = i2d_X509( - cert, &netreq->debug_tls_peer_cert.data); - X509_free(cert); + netreq->upstream->tls_obj) { + if (netreq->debug_tls_peer_cert.data == NULL && + (cert = SSL_get_peer_certificate(netreq->upstream->tls_obj))) { + netreq->debug_tls_peer_cert.size = i2d_X509( + cert, &netreq->debug_tls_peer_cert.data); + X509_free(cert); + } + netreq->debug_tls_version = SSL_get_version(netreq->upstream->tls_obj); } /* Need this because auth status is reset on connection close */ netreq->debug_tls_auth_status = netreq->upstream->tls_auth_state; diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index c3dcad9b..300bffbf 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -54,6 +54,13 @@ #define EDNS0_PADDING_CODE 12 +static const char TLS13_CIPHER_SUITE[] = + "TLS13-AES-256-GCM-SHA384:" + "TLS13-CHACHA20-POLY1305-SHA256:" + "TLS13-AES-128-GCM-SHA256:" + "TLS13-AES-128-CCM-8-SHA256:" + "TLS13-AES-128-CCM-SHA256"; + #define EXAMPLE_PIN "pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\"" /* Plugin exit values */ @@ -298,6 +305,7 @@ static void usage() " Check server support for EDNS0 padding in TLS\n" " Special blocksize values are 0 = off,\n" " 1 = sensible default.\n" +" tls-1.3 Check whether server supports TLS 1.3\n" "\n" "Enabling monitoring mode ensures output messages and exit statuses conform\n" "to the requirements of monitoring plugins (www.monitoring-plugins.org).\n", @@ -386,7 +394,8 @@ static exit_value search(const struct test_info_s *test_info, const char *name, uint16_t type, getdns_dict *extensions, - getdns_dict **response) + getdns_dict **response, + getdns_return_t *getdns_return) { getdns_return_t ret; getdns_dict *search_extensions = @@ -394,6 +403,8 @@ static exit_value search(const struct test_info_s *test_info, /* We always turn on the return_call_reporting extension. */ if ((ret = getdns_dict_set_int(search_extensions, "return_call_reporting", GETDNS_EXTENSION_TRUE)) != GETDNS_RETURN_GOOD) { + if (getdns_return) + *getdns_return = ret; fprintf(test_info->errout, "Cannot set return call reporting: %s (%d)", getdns_get_errorstr_by_id(ret), @@ -417,11 +428,15 @@ static exit_value search(const struct test_info_s *test_info, if (!extensions) getdns_dict_destroy(search_extensions); if (ret != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Error resolving '%s': %s (%d)", - name, - getdns_get_errorstr_by_id(ret), - ret); + if (getdns_return) { + *getdns_return = ret; + } else { + fprintf(test_info->errout, + "Error resolving '%s': %s (%d)", + name, + getdns_get_errorstr_by_id(ret), + ret); + } return EXIT_CRITICAL; } @@ -431,6 +446,8 @@ static exit_value search(const struct test_info_s *test_info, getdns_pretty_print_dict(*response)); } + if (getdns_return) + *getdns_return = ret; return EXIT_OK; } @@ -694,7 +711,7 @@ static exit_value search_check(const struct test_info_s *test_info, exit_value xit; getdns_dict *resp; - if ((xit = search(test_info, lookup_name, lookup_type, NULL, &resp)) != EXIT_OK) + if ((xit = search(test_info, lookup_name, lookup_type, NULL, &resp, NULL)) != EXIT_OK) return xit; if ((xit = check_result(test_info, resp)) != EXIT_OK) @@ -1170,7 +1187,8 @@ static exit_value test_dnssec_validate(const struct test_info_s *test_info, "dnssec-failed.org", GETDNS_RRTYPE_A, NULL, - &response)) != EXIT_OK) + &response, + NULL)) != EXIT_OK) return xit; uint32_t error_id, rcode; @@ -1209,7 +1227,8 @@ static exit_value test_dnssec_validate(const struct test_info_s *test_info, "dnssec-failed.org", GETDNS_RRTYPE_A, extensions, - &response2)) != EXIT_OK) + &response2, + NULL)) != EXIT_OK) return xit; getdns_dict_destroy(extensions); @@ -1238,6 +1257,100 @@ static exit_value test_dnssec_validate(const struct test_info_s *test_info, return EXIT_OK; } +static exit_value test_tls13(const struct test_info_s *test_info, + char ** av) +{ + exit_value xit; + getdns_return_t ret; + + if (*av) { + fputs("tls-1.3 takes no arguments", test_info->errout); + return EXIT_USAGE; + } + + /* + * Set cipher list to TLS 1.3-only ciphers. If we are using + * an OpenSSL version that doesn't support TLS 1.3 this will cause + * a Bad Context error on the lookup. + */ + if ((ret = getdns_context_set_tls_cipher_list(test_info->context, TLS13_CIPHER_SUITE)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot set TLS 1.3 cipher list: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + } + + getdns_dict *response; + + if ((xit = search(test_info, + DEFAULT_LOOKUP_NAME, + DEFAULT_LOOKUP_TYPE, + NULL, + &response, + &ret)) != EXIT_OK) { + if (xit == EXIT_CRITICAL) { + if (ret == GETDNS_RETURN_BAD_CONTEXT) { + fputs("Your version of OpenSSL does not support TLS 1.3 ciphers. You need at least OpenSSL 1.1.1.", + test_info->errout); + return EXIT_UNKNOWN; + } else { + fputs("Cannot establish TLS 1.3 connection.", + test_info->errout); + return EXIT_CRITICAL; + } + } else { + return xit; + } + } + + if ((xit = get_report_info(test_info, 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 TLS, so tls_version flag must exist. */ + getdns_bindata *version; + + if ((ret = getdns_dict_get_bindata(d, "tls_version", &version)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Cannot get TLS version: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + + const char TLS_1_3_SIG[] = "TLSv1.3"; + + if (strncmp((const char *)version->data, TLS_1_3_SIG, sizeof(TLS_1_3_SIG) - 1) != 0) { + fputs("Server does not support TLS 1.3", test_info->errout); + return EXIT_CRITICAL; + } + + if ((xit = check_result(test_info, response)) != EXIT_OK) + return xit; + + fputs("Server supports TLS 1.3", test_info->errout); + return EXIT_OK; +} + static struct test_funcs_s { const char *name; @@ -1254,6 +1367,7 @@ static struct test_funcs_s { "tls-padding", true, false, test_padding }, { "keepalive", false, true, test_keepalive }, { "dnssec-validate", false, false, test_dnssec_validate }, + { "tls-1.3", true, false, test_tls13 }, { NULL, false, false, NULL } }; diff --git a/src/types-internal.h b/src/types-internal.h index 3199b134..76615625 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -242,6 +242,7 @@ typedef struct getdns_network_req uint64_t debug_end_time; getdns_auth_state_t debug_tls_auth_status; getdns_bindata debug_tls_peer_cert; + const char *debug_tls_version; size_t debug_udp; /* When more space is needed for the wire_data response than is diff --git a/src/util-internal.c b/src/util-internal.c index 3ec15995..0bceb002 100644 --- a/src/util-internal.c +++ b/src/util-internal.c @@ -920,6 +920,12 @@ _getdns_create_call_reporting_dict( getdns_dict_destroy(netreq_debug); return NULL; } + if (getdns_dict_util_set_string(netreq_debug, "tls_version", + netreq->debug_tls_version)){ + + getdns_dict_destroy(netreq_debug); + return NULL; + } if (getdns_dict_set_bindata(netreq_debug, "tls_peer_cert", &netreq->debug_tls_peer_cert)) { From f9e4c9f8532bb5c107fdeef92490e3f03c882818 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 22 Jan 2018 14:36:54 +0000 Subject: [PATCH 32/47] Revise output. If in monitoring mode, make output conform to Nagios norms. This starts with the probe type and result, so we need to save output generated during the operation and print it at the end. If not in monitoring mode, make the formatting more expansive. --- src/tools/getdns_server_mon.c | 611 ++++++++++++++++++++-------------- 1 file changed, 364 insertions(+), 247 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 300bffbf..ab711975 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -26,6 +26,7 @@ */ #include +#include #include #include #include @@ -80,19 +81,39 @@ typedef enum { VERBOSITY_DEBUG } plugin_verbosity; +#define MAX_BASE_OUTPUT_LEN 256 +#define MAX_PERF_OUTPUT_LEN 256 + static struct test_info_s { getdns_context *context; - /* Output control */ + /* Output */ bool monitoring; FILE *errout; plugin_verbosity verbosity; + bool debug_output; + char base_output[MAX_BASE_OUTPUT_LEN + 1]; + char perf_output[MAX_PERF_OUTPUT_LEN + 1]; /* Test config info */ bool fail_on_dns_errors; } test_info; +static void snprintcat(char *buf, size_t buflen, const char *format, ...) +{ + va_list ap; + int l = strlen(buf); + + buf += l; + buflen -= l; + + va_start(ap, format); + vsnprintf(buf, buflen, format, ap); + va_end(ap); + buf[buflen] = '\0'; +} + static int get_rrtype(const char *t) { char buf[1024] = "GETDNS_RRTYPE_"; @@ -263,7 +284,7 @@ static void usage() { fputs( "Usage: " APP_NAME " [-M] [-E] [(-u|-t|-T)] [-S] [-K ]\n" -" [-v [-v [-v]]] [-V] @upstream testname [] []\n" +" [-v [-v [-v]]] [-V] @upstream testname []\n" " -M|--monitoring Make output suitable for monitoring tools\n" " -E|--fail-on-dns-errors Fail on DNS error (NXDOMAIN, SERVFAIL)\n" " -u|--udp Use UDP transport\n" @@ -272,6 +293,7 @@ static void usage() " -S|--strict-usage-profile Use strict profile (require authentication)\n" " -K|--spki-pin SPKI pin for TLS connections (can repeat)\n" " -v|--verbose Increase output verbosity\n" +" -D|--debug Enable debugging output\n" " -V|--version Report GetDNS version\n" "\n" "spki-pin: Should look like '" EXAMPLE_PIN "'\n" @@ -364,7 +386,7 @@ static void get_thresholds(char ***av, return; } -static exit_value get_name_type_args(const struct test_info_s *test_info, +static exit_value get_name_type_args(struct test_info_s *test_info, char ***av, const char **lookup_name, uint32_t *lookup_type) @@ -373,7 +395,7 @@ static exit_value get_name_type_args(const struct test_info_s *test_info, if (strlen(**av) > 0) { *lookup_name = **av; } else { - fputs("Empty name not valid", test_info->errout); + strcpy(test_info->base_output, "Empty name not valid"); return EXIT_UNKNOWN; } ++*av; @@ -390,7 +412,7 @@ static exit_value get_name_type_args(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value search(const struct test_info_s *test_info, +static exit_value search(struct test_info_s *test_info, const char *name, uint16_t type, getdns_dict *extensions, @@ -405,19 +427,24 @@ static exit_value search(const struct test_info_s *test_info, if ((ret = getdns_dict_set_int(search_extensions, "return_call_reporting", GETDNS_EXTENSION_TRUE)) != GETDNS_RETURN_GOOD) { if (getdns_return) *getdns_return = ret; - fprintf(test_info->errout, - "Cannot set return call reporting: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot set return call reporting: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); if (!extensions) getdns_dict_destroy(search_extensions); return EXIT_UNKNOWN; } - if (test_info->verbosity >= VERBOSITY_DEBUG) { - fprintf(test_info->errout, - "Context: %s\n", - getdns_pretty_print_dict(getdns_context_get_api_information(test_info->context))); + if (test_info->verbosity >= VERBOSITY_ADDITIONAL && + !test_info->monitoring) { + printf("Lookup:\t\t\t%s %u\n", name, type); + } + + if (test_info->debug_output) { + printf("Context: %s\n", + getdns_pretty_print_dict(getdns_context_get_api_information(test_info->context))); } ret = getdns_general_sync(test_info->context, @@ -431,19 +458,19 @@ static exit_value search(const struct test_info_s *test_info, if (getdns_return) { *getdns_return = ret; } else { - fprintf(test_info->errout, - "Error resolving '%s': %s (%d)", - name, - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Error resolving '%s': %s (%d)", + name, + getdns_get_errorstr_by_id(ret), + ret); } return EXIT_CRITICAL; } - if (test_info->verbosity >= VERBOSITY_DEBUG) { - fprintf(test_info->errout, - "Response: %s\n", - getdns_pretty_print_dict(*response)); + if (test_info->debug_output) { + printf("Response: %s\n", + getdns_pretty_print_dict(*response)); } if (getdns_return) @@ -451,7 +478,7 @@ static exit_value search(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value get_result(const struct test_info_s *test_info, +static exit_value get_result(struct test_info_s *test_info, const getdns_dict *response, uint32_t *error_id, uint32_t *rcode) @@ -459,10 +486,11 @@ static exit_value get_result(const struct test_info_s *test_info, getdns_return_t ret; if ((ret = getdns_dict_get_int(response, "status", error_id)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get result status: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get result status: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } @@ -472,17 +500,18 @@ static exit_value get_result(const struct test_info_s *test_info, } if ((ret = getdns_dict_get_int(response, "/replies_tree/0/header/rcode", rcode)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get DNS return code: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get DNS return code: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } return EXIT_OK; } -static exit_value check_result(const struct test_info_s *test_info, +static exit_value check_result(struct test_info_s *test_info, const getdns_dict *response) { exit_value xit; @@ -493,15 +522,15 @@ static exit_value check_result(const struct test_info_s *test_info, switch(error_id) { case GETDNS_RESPSTATUS_ALL_TIMEOUT: - fputs("Search timed out", test_info->errout); + strcpy(test_info->base_output, "Search timed out"); return EXIT_CRITICAL; case GETDNS_RESPSTATUS_NO_SECURE_ANSWERS: - fputs("No secure answers", test_info->errout); + strcpy(test_info->base_output, "No secure answers"); return EXIT_CRITICAL; case GETDNS_RESPSTATUS_ALL_BOGUS_ANSWERS: - fputs("All answers are bogus", test_info->errout); + strcpy(test_info->base_output, "All answers are bogus"); return EXIT_CRITICAL; default: @@ -509,24 +538,31 @@ static exit_value check_result(const struct test_info_s *test_info, } if (test_info->verbosity >= VERBOSITY_ADDITIONAL){ - fprintf(test_info->errout, - "result: %s (%d), ", - getdns_get_errorstr_by_id(error_id), - error_id); + if (test_info->monitoring) { + snprintcat(test_info->perf_output, + MAX_PERF_OUTPUT_LEN, + "result=%d;", + error_id); + } else { + printf("DNS result:\t\t%s (%d)\n", + getdns_get_errorstr_by_id(error_id), + error_id); + } } if (test_info->fail_on_dns_errors && rcode > 0) { - fprintf(test_info->errout, - "DNS error %s (%d)", - rcode_text(rcode), - rcode); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "DNS error %s (%d)", + rcode_text(rcode), + rcode); return EXIT_CRITICAL; } return EXIT_OK; } -static exit_value get_report_info(const struct test_info_s *test_info, +static exit_value get_report_info(struct test_info_s *test_info, const getdns_dict *response, uint32_t *rtt, getdns_bindata **auth_status, @@ -539,80 +575,120 @@ static exit_value get_report_info(const struct test_info_s *test_info, time_t cert_expire_time_val = 0; 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); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "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); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get call report first item: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } if (test_info->verbosity >= VERBOSITY_ADDITIONAL) { uint32_t transport; + const char *transport_text = "???"; + if ((ret = getdns_dict_get_int(d, "transport", &transport)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get transport: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get transport: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } + switch(transport) { case GETDNS_TRANSPORT_UDP: - fputs("UDP, ", test_info->errout); + transport_text = "UDP"; break; case GETDNS_TRANSPORT_TCP: - fputs("TCP, ", test_info->errout); + transport_text = "TCP"; break; case GETDNS_TRANSPORT_TLS: - fputs("TLS, ", test_info->errout); - break; - - default: - fputs("???, ", test_info->errout); + transport_text = "TLS"; break; } + + if (test_info->monitoring) { + snprintcat(test_info->perf_output, + MAX_PERF_OUTPUT_LEN, + "transport=%s;", + transport_text); + } else { + printf("Transport:\t\t%s\n", transport_text); + } } + if ((ret = getdns_dict_get_int(d, "run_time/ms", &rtt_val)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get RTT: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get RTT: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } if (rtt) *rtt = rtt_val; - if (test_info->verbosity >= VERBOSITY_ADDITIONAL) - fprintf(test_info->errout, "RTT %dms, ", rtt_val); + if (test_info->verbosity >= VERBOSITY_ADDITIONAL) { + if (test_info->monitoring) { + snprintcat(test_info->perf_output, + MAX_PERF_OUTPUT_LEN, + "rtt=%dms;", + rtt_val); + } else { + printf("RTT:\t\t\t%dms\n", rtt_val); + } + } if (getdns_dict_get_bindata(d, "tls_auth_status", &auth_status_val) == GETDNS_RETURN_GOOD) { + const char *auth_status_text = (char *) auth_status_val->data; + /* Just in case - not sure this is necessary */ auth_status_val->data[auth_status_val->size] = '\0'; - if (test_info->verbosity >= VERBOSITY_ADDITIONAL) - fprintf(test_info->errout, "auth. %s, ", (char *) auth_status_val->data); + if (test_info->verbosity >= VERBOSITY_ADDITIONAL) { + if (test_info->monitoring) { + snprintcat(test_info->perf_output, + MAX_PERF_OUTPUT_LEN, + "auth=%s;", + auth_status_text); + } else { + printf("Authentication:\t\t%s\n", auth_status_text); + } + } } if (auth_status) *auth_status = auth_status_val; getdns_bindata *cert; + if (getdns_dict_get_bindata(d, "tls_peer_cert", &cert) == GETDNS_RETURN_GOOD) { if (!extract_cert_expiry(cert->data, cert->size, &cert_expire_time_val)) { - fputs("Cannot parse PKIX certificate", test_info->errout); + strcpy(test_info->base_output, "Cannot parse PKIX certificate"); return EXIT_UNKNOWN; } if (test_info->verbosity >= VERBOSITY_ADDITIONAL) { struct tm *tm = gmtime(&cert_expire_time_val); char buf[25]; - strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC", tm); - fprintf(test_info->errout, "cert expiry %s, ", buf); + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm); + if (test_info->monitoring) { + snprintcat(test_info->perf_output, + MAX_PERF_OUTPUT_LEN, + "expire=%s;", + buf); + } else { + printf("Certicate expires:\t%s UTC\n", buf); + } } } if (cert_expire_time) @@ -621,7 +697,7 @@ static exit_value get_report_info(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value get_answers(const struct test_info_s *test_info, +static exit_value get_answers(struct test_info_s *test_info, const getdns_dict *response, const char *section, getdns_list **answers, @@ -633,33 +709,36 @@ static exit_value get_answers(const struct test_info_s *test_info, snprintf(buf, sizeof(buf), "/replies_tree/0/%s", section); if ((ret = getdns_dict_get_list(response, buf, answers)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get section '%s': %s (%d)", - section, - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get section '%s': %s (%d)", + section, + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } if ((ret = getdns_list_get_length(*answers, no_answers)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get number of items in '%s': %s (%d)", - section, - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get number of items in '%s': %s (%d)", + section, + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } if (*no_answers <= 0) { - fprintf(test_info->errout, - "Zero entries in '%s'", - section); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Zero entries in '%s'", + section); return EXIT_WARNING; } return EXIT_OK; } -static exit_value check_answer_type(const struct test_info_s *test_info, +static exit_value check_answer_type(struct test_info_s *test_info, const getdns_dict *response, uint32_t rrtype) { @@ -675,32 +754,34 @@ static exit_value check_answer_type(const struct test_info_s *test_info, getdns_return_t ret; if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get answer number %zu: %s (%d)", - i, - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get answer number %zu: %s (%d)", + i, + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } uint32_t rtype; if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get answer type: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get answer type: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } if (rtype == rrtype) return EXIT_OK; } - fputs("Answer does not contain expected type", test_info->errout); + strcpy(test_info->base_output, "Answer does not contain expected type"); return EXIT_UNKNOWN; } -static exit_value search_check(const struct test_info_s *test_info, +static exit_value search_check(struct test_info_s *test_info, const char *lookup_name, uint16_t lookup_type, getdns_dict **response, @@ -728,7 +809,7 @@ static exit_value search_check(const struct test_info_s *test_info, return xit; } -static exit_value parse_search_check(const struct test_info_s *test_info, +static exit_value parse_search_check(struct test_info_s *test_info, char **av, const char *usage, getdns_dict **response, @@ -744,7 +825,7 @@ static exit_value parse_search_check(const struct test_info_s *test_info, return xit; if (*av) { - fputs(usage, test_info->errout); + strcpy(test_info->base_output, usage); return EXIT_USAGE; } @@ -758,7 +839,7 @@ static exit_value parse_search_check(const struct test_info_s *test_info, ** Test routines. **/ -static exit_value test_lookup(const struct test_info_s *test_info, +static exit_value test_lookup(struct test_info_s *test_info, char ** av) { exit_value xit; @@ -772,11 +853,11 @@ static exit_value test_lookup(const struct test_info_s *test_info, NULL)) != EXIT_OK) return xit; - fputs("Lookup succeeded", test_info->errout); + strcpy(test_info->base_output, "Lookup succeeded"); return EXIT_OK; } -static exit_value test_rtt(const struct test_info_s *test_info, +static exit_value test_rtt(struct test_info_s *test_info, char ** av) { exit_value xit; @@ -795,7 +876,10 @@ static exit_value test_rtt(const struct test_info_s *test_info, NULL)) != EXIT_OK) return xit; - fputs("RTT lookup succeeded", test_info->errout); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "RTT lookup succeeded in %dms", + rtt_val); if ((int) rtt_val > critical_ms) return EXIT_CRITICAL; @@ -804,7 +888,7 @@ static exit_value test_rtt(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value test_authenticate(const struct test_info_s *test_info, +static exit_value test_authenticate(struct test_info_s *test_info, char ** av) { exit_value xit; @@ -820,15 +904,15 @@ static exit_value test_authenticate(const struct test_info_s *test_info, return xit; if (!auth_status || strcmp((char *) auth_status->data, "Success") != 0) { - fputs("Authentication failed", test_info->errout); + strcpy(test_info->base_output, "Authentication failed"); return EXIT_CRITICAL; } else { - fputs("Authentication succeeded", test_info->errout); + strcpy(test_info->base_output, "Authentication succeeded"); return EXIT_OK; } } -static exit_value test_certificate_valid(const struct test_info_s *test_info, +static exit_value test_certificate_valid(struct test_info_s *test_info, char **av) { exit_value xit; @@ -849,7 +933,7 @@ static exit_value test_certificate_valid(const struct test_info_s *test_info, if (expire_time == 0) { - fputs("No PKIX certificate", test_info->errout); + strcpy(test_info->base_output, "No PKIX certificate"); return EXIT_CRITICAL; } @@ -857,19 +941,21 @@ static exit_value test_certificate_valid(const struct test_info_s *test_info, int days_to_expiry = (expire_time - now) / 86400; if (days_to_expiry < 0) { - fprintf(test_info->errout, - "Certificate expired %d day%s ago", - -days_to_expiry, - (days_to_expiry < -1) ? "s" : ""); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Certificate expired %d day%s ago", + -days_to_expiry, + (days_to_expiry < -1) ? "s" : ""); return EXIT_CRITICAL; } if (days_to_expiry == 0) { - fputs("Certificate expires today", test_info->errout); + strcpy(test_info->base_output, "Certificate expires today"); } else { - fprintf(test_info->errout, - "Certificate will expire in %d day%s", - days_to_expiry, - (days_to_expiry > 1) ? "s" : ""); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Certificate will expire in %d day%s", + days_to_expiry, + (days_to_expiry > 1) ? "s" : ""); } if (days_to_expiry <= critical_days) { return EXIT_CRITICAL; @@ -880,12 +966,11 @@ static exit_value test_certificate_valid(const struct test_info_s *test_info, return EXIT_OK; } -static exit_value test_qname_minimisation(const struct test_info_s *test_info, +static exit_value test_qname_minimisation(struct test_info_s *test_info, char ** av) { if (*av) { - fputs("qname-min takes no arguments", - test_info->errout); + strcpy(test_info->base_output, "qname-min takes no arguments"); return EXIT_USAGE; } @@ -912,21 +997,23 @@ static exit_value test_qname_minimisation(const struct test_info_s *test_info, getdns_return_t ret; if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get answer number %zu: %s (%d)", - i, - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get answer number %zu: %s (%d)", + i, + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } uint32_t rtype; if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get answer type: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get answer type: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } if (rtype != GETDNS_RRTYPE_TXT) @@ -935,19 +1022,19 @@ static exit_value test_qname_minimisation(const struct test_info_s *test_info, getdns_bindata *rtxt; if ((ret = getdns_dict_get_bindata(answer, "/rdata/txt_strings/0", &rtxt)) != GETDNS_RETURN_GOOD) { - fputs("No answer text", test_info->errout); + strcpy(test_info->base_output, "No answer text"); return EXIT_UNKNOWN; } if (rtxt->size > 0 ) { switch(rtxt->data[0]) { case 'H': - fputs("QNAME minimisation ON", test_info->errout); + strcpy(test_info->base_output, "QNAME minimisation ON"); return EXIT_OK; case 'N': - fputs("QNAME minimisation OFF", test_info->errout); - return EXIT_WARNING; + strcpy(test_info->base_output, "QNAME minimisation OFF"); + return EXIT_CRITICAL; default: /* Unrecognised message. */ @@ -956,11 +1043,11 @@ static exit_value test_qname_minimisation(const struct test_info_s *test_info, } } - fputs("No valid QNAME minimisation data", test_info->errout); + strcpy(test_info->base_output, "No valid QNAME minimisation data"); return EXIT_UNKNOWN; } -static exit_value test_padding(const struct test_info_s *test_info, +static exit_value test_padding(struct test_info_s *test_info, char ** av) { getdns_dict *response; @@ -970,17 +1057,18 @@ static exit_value test_padding(const struct test_info_s *test_info, const char USAGE[] = "padding takes arguments [ []]"; if (!*av || (blocksize = strtol(*av, &endptr, 10), *endptr != '\0' || blocksize < 0)) { - fputs(USAGE, test_info->errout); + strcpy(test_info->base_output, USAGE); return EXIT_USAGE; } ++av; getdns_return_t ret; if ((ret = getdns_context_set_tls_query_padding_blocksize(test_info->context, (uint16_t) blocksize)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot set padding blocksize: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot set padding blocksize: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } @@ -1004,21 +1092,23 @@ static exit_value test_padding(const struct test_info_s *test_info, getdns_return_t ret; if ((ret = getdns_list_get_dict(answers, i, &answer)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get answer number %zu: %s (%d)", - i, - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get answer number %zu: %s (%d)", + i, + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } uint32_t rtype; if ((ret = getdns_dict_get_int(answer, "type", &rtype)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get answer type: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get answer type: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } if (rtype != GETDNS_RRTYPE_OPT) @@ -1031,10 +1121,11 @@ static exit_value test_padding(const struct test_info_s *test_info, goto no_padding; } if ((ret = getdns_list_get_length(options, &no_options)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get number of options: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get number of options: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } @@ -1043,18 +1134,20 @@ static exit_value test_padding(const struct test_info_s *test_info, uint32_t code; if ((ret = getdns_list_get_dict(options, j, &option)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get option number %zu: %s (%d)", - j, - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get option number %zu: %s (%d)", + j, + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } if ((ret = getdns_dict_get_int(option, "option_code", &code)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get option code: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get option code: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } @@ -1065,26 +1158,28 @@ static exit_value test_padding(const struct test_info_s *test_info, getdns_bindata *data; if ((ret = getdns_dict_get_bindata(option, "option_data", &data)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get option code: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get option code: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } - fprintf(test_info->errout, - "Padding found, length %zu", - data->size); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Padding found, length %zu", + data->size); return EXIT_OK; } } no_padding: - fputs("No padding found", test_info->errout); + strcpy(test_info->base_output, "No padding found"); return EXIT_CRITICAL; } -static exit_value test_keepalive(const struct test_info_s *test_info, +static exit_value test_keepalive(struct test_info_s *test_info, char ** av) { getdns_dict *response; @@ -1094,17 +1189,18 @@ static exit_value test_keepalive(const struct test_info_s *test_info, const char USAGE[] = "keepalive takes arguments [ []]"; if (!*av || (timeout = strtoll(*av, &endptr, 10), *endptr != '\0' || timeout < 0)) { - fputs(USAGE, test_info->errout); + strcpy(test_info->base_output, USAGE); 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); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot set keepalive timeout: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } @@ -1120,29 +1216,32 @@ static exit_value test_keepalive(const struct test_info_s *test_info, 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); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "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); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "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); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get server keepalive flag: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } @@ -1152,31 +1251,34 @@ static exit_value test_keepalive(const struct test_info_s *test_info, 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); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "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); + strcpy(test_info->base_output, "Server sent keepalive, idle timeout now (overflow)"); } else { - fprintf(test_info->errout, "Server sent keepalive, idle timeout now %ums", t); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Server sent keepalive, idle timeout now %ums", + t); } return EXIT_OK; } else { - fputs("Server did not send keepalive", test_info->errout); + strcpy(test_info->base_output, "Server did not send keepalive"); return EXIT_CRITICAL; } } -static exit_value test_dnssec_validate(const struct test_info_s *test_info, +static exit_value test_dnssec_validate(struct test_info_s *test_info, char ** av) { if (*av) { - fputs("dnssec-validate takes no arguments", - test_info->errout); + strcpy(test_info->base_output, "dnssec-validate takes no arguments"); return EXIT_USAGE; } @@ -1197,12 +1299,12 @@ static exit_value test_dnssec_validate(const struct test_info_s *test_info, return xit; if (error_id == GETDNS_RESPSTATUS_ALL_TIMEOUT) { - fputs("Search timed out", test_info->errout); + strcpy(test_info->base_output, "Search timed out"); return EXIT_CRITICAL; } if (rcode != GETDNS_RCODE_SERVFAIL) { - fputs("Server does NOT validate DNSSEC", test_info->errout); + strcpy(test_info->base_output, "Server does NOT validate DNSSEC"); return EXIT_CRITICAL; } @@ -1215,10 +1317,11 @@ static exit_value test_dnssec_validate(const struct test_info_s *test_info, getdns_dict *extensions = getdns_dict_create(); if ((ret = getdns_dict_set_int(extensions, "/header/cd", 1)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot set CD bit: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot set CD bit: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); getdns_dict_destroy(extensions); return EXIT_UNKNOWN; } @@ -1244,27 +1347,27 @@ static exit_value test_dnssec_validate(const struct test_info_s *test_info, return xit; if (error_id == GETDNS_RESPSTATUS_ALL_TIMEOUT) { - fputs("Search timed out", test_info->errout); + strcpy(test_info->base_output, "Search timed out"); return EXIT_CRITICAL; } if (error_id != GETDNS_RESPSTATUS_GOOD || rcode != GETDNS_RCODE_NOERROR) { - fputs("Server error - cannot determine DNSSEC status", test_info->errout); + strcpy(test_info->base_output, "Server error - cannot determine DNSSEC status"); return EXIT_UNKNOWN; } - fputs("Server validates DNSSEC", test_info->errout); + strcpy(test_info->base_output, "Server validates DNSSEC"); return EXIT_OK; } -static exit_value test_tls13(const struct test_info_s *test_info, +static exit_value test_tls13(struct test_info_s *test_info, char ** av) { exit_value xit; getdns_return_t ret; if (*av) { - fputs("tls-1.3 takes no arguments", test_info->errout); + strcpy(test_info->base_output, "tls-1.3 takes no arguments"); return EXIT_USAGE; } @@ -1274,10 +1377,12 @@ static exit_value test_tls13(const struct test_info_s *test_info, * a Bad Context error on the lookup. */ if ((ret = getdns_context_set_tls_cipher_list(test_info->context, TLS13_CIPHER_SUITE)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot set TLS 1.3 cipher list: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot set TLS 1.3 cipher list: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; } getdns_dict *response; @@ -1290,12 +1395,10 @@ static exit_value test_tls13(const struct test_info_s *test_info, &ret)) != EXIT_OK) { if (xit == EXIT_CRITICAL) { if (ret == GETDNS_RETURN_BAD_CONTEXT) { - fputs("Your version of OpenSSL does not support TLS 1.3 ciphers. You need at least OpenSSL 1.1.1.", - test_info->errout); + strcpy(test_info->base_output, "Your version of OpenSSL does not support TLS 1.3 ciphers. You need at least OpenSSL 1.1.1."); return EXIT_UNKNOWN; } else { - fputs("Cannot establish TLS 1.3 connection.", - test_info->errout); + strcpy(test_info->base_output, "Cannot establish TLS 1.3 connection."); return EXIT_CRITICAL; } } else { @@ -1309,20 +1412,22 @@ static exit_value test_tls13(const struct test_info_s *test_info, 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); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "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); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get call report first item: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } @@ -1330,24 +1435,25 @@ static exit_value test_tls13(const struct test_info_s *test_info, getdns_bindata *version; if ((ret = getdns_dict_get_bindata(d, "tls_version", &version)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Cannot get TLS version: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Cannot get TLS version: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } const char TLS_1_3_SIG[] = "TLSv1.3"; if (strncmp((const char *)version->data, TLS_1_3_SIG, sizeof(TLS_1_3_SIG) - 1) != 0) { - fputs("Server does not support TLS 1.3", test_info->errout); + strcpy(test_info->base_output, "Server does not support TLS 1.3"); return EXIT_CRITICAL; } if ((xit = check_result(test_info, response)) != EXIT_OK) return xit; - fputs("Server supports TLS 1.3", test_info->errout); + strcpy(test_info->base_output, "Server supports TLS 1.3"); return EXIT_OK; } @@ -1356,7 +1462,7 @@ static struct test_funcs_s const char *name; bool implies_tls; bool implies_tcp; - exit_value (*func)(const struct test_info_s *test_info, char **av); + exit_value (*func)(struct test_info_s *test_info, char **av); } TESTS[] = { { "lookup", false, false, test_lookup }, @@ -1398,6 +1504,9 @@ int main(int ac, char *av[]) strcmp(*av, "--monitoring") == 0) { test_info.monitoring = true; test_info.errout = stdout; + } else if (strcmp(*av, "-D") == 0 || + strcmp(*av, "--debug") == 0) { + test_info.debug_output = true; } else if (strcmp(*av, "-E") == 0 || strcmp(*av, "--fail-on-dns-errors") == 0) { test_info.fail_on_dns_errors = true; @@ -1607,32 +1716,40 @@ int main(int ac, char *av[]) } exit_value xit = f->func(&test_info, av); + const char *xit_text = "(\?\?\?)"; + FILE *out = stdout; + switch(xit) { case EXIT_OK: - fputs(" (OK)", test_info.errout); + xit_text = "OK"; break; case EXIT_WARNING: - fputs(" (WARNING)", test_info.errout); + xit_text = "WARNING"; break; case EXIT_CRITICAL: - fputs(" (CRITICAL)", test_info.errout); + xit_text = "CRITICAL"; break; case EXIT_UNKNOWN: - fputs(" (UNKNOWN)", test_info.errout); - break; - case EXIT_USAGE: xit = EXIT_UNKNOWN; - break; - - default: - /* ??? is a trigraph... */ - fputs(" (\?\?\?)", test_info.errout); + xit_text = "UNKNOWN"; + out = test_info.errout; break; } - fputc('\n', test_info.errout); + + if (test_info.monitoring) + fprintf(out, "DNS SERVER %s - ", xit_text); + fputs(test_info.base_output, out); + + if (test_info.verbosity >= VERBOSITY_ADDITIONAL && + test_info.monitoring && + strlen(test_info.perf_output) > 0) { + fprintf(out, "|%s", test_info.perf_output); + } + + fputc('\n', out); exit(xit); } From 8c3047dbe019881b3fbd9f90108ba11cf856d51c Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Sun, 21 Jan 2018 21:38:58 +0000 Subject: [PATCH 33/47] Add 'concurrent' test The concurrent test works by sending a known good query synchronously, and then sending asynchronous queries for three random TLDs followed by the known good query. The latter should be answerable from cache, and so give a result before at least one of the random TLDs. --- src/tools/getdns_server_mon.c | 130 ++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index ab711975..2f8cd182 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -305,6 +305,8 @@ static void usage() "tsig spec: [:]:\n" "\n" "Tests:\n" +" concurrent Check server support for concurrent query\n" +" processing\n" " lookup [ []] Check lookup on server\n" " keepalive [ []]\n" " Check server support for EDNS0 keepalive in TCP/TLS\n" @@ -1457,6 +1459,133 @@ static exit_value test_tls13(struct test_info_s *test_info, return EXIT_OK; } +struct async_query +{ + const char *name; + unsigned done_order; + bool error; +}; + +static void concurrent_callback(getdns_context *context, + getdns_callback_type_t callback_type, + getdns_dict *response, + void *userarg, + getdns_transaction_t transaction_id) +{ + (void) context; + (void) callback_type; + (void) response; + (void) transaction_id; + + struct async_query *query = (struct async_query *) userarg; + static unsigned callback_no; + + query->done_order = ++callback_no; + query->error = (callback_type != GETDNS_CALLBACK_COMPLETE); +} + +static exit_value test_concurrent(const struct test_info_s *test_info, + char ** av) +{ + if (*av) { + fputs("concurrent takes no arguments", + test_info->errout); + return EXIT_USAGE; + } + + /* Three names with random TLDs. */ + const int NAMELEN = 30; + char names[3][NAMELEN + 1]; + + srandom((unsigned int) time(NULL)); + + for (int i = 0; i < 3; ++i) { + const char ROOT[] = "getdnsapi.dx"; + strcpy(names[i], ROOT); + for (int j = sizeof(ROOT) - 1; j < NAMELEN; ++j) { + names[i][j] = 'a' + random() % ('z' - 'a'); + } + names[i][NAMELEN] = '\0'; + } + + /* A set of asynchronous queries to send. One exists. */ + const char GOOD_NAME[] = "getdnsapi.net"; + struct async_query async_queries[] = { + { names[0], 0, false }, + { names[1], 0, false }, + { names[2], 0, false }, + { GOOD_NAME, 0, false } + }; + unsigned NQUERIES = sizeof(async_queries) / sizeof(async_queries[0]); + + /* Pre-load the cache with result of the good query. */ + getdns_dict *response; + exit_value xit; + + if ((xit = search_check(test_info, + GOOD_NAME, + GETDNS_RRTYPE_AAAA, + &response, + NULL, + NULL, + NULL)) != EXIT_OK) + return xit; + + /* Now launch async searches for all the above. */ + if (test_info->verbosity >= VERBOSITY_ADDITIONAL){ + fputs("concurrent search: ", test_info->errout); + } + for (unsigned i = 0; i < NQUERIES; ++i) { + getdns_return_t ret; + + if ((ret = getdns_general(test_info->context, + async_queries[i].name, + GETDNS_RRTYPE_AAAA, + NULL, + &async_queries[i], + NULL, + concurrent_callback)) != GETDNS_RETURN_GOOD) { + fprintf(test_info->errout, + "Async search failed: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); + return EXIT_UNKNOWN; + } + if (test_info->verbosity >= VERBOSITY_ADDITIONAL){ + fprintf(test_info->errout, + "[%u] %s, ", + i, async_queries[i].name); + } + } + + /* And wait for them to complete. */ + getdns_context_run(test_info->context); + if (test_info->verbosity >= VERBOSITY_ADDITIONAL){ + fputs("concurrent result: ", test_info->errout); + } + + for (unsigned i = 0; i < NQUERIES; ++i) { + if (async_queries[i].error) { + fputs("Query did not complete", test_info->errout); + return EXIT_UNKNOWN; + } + if (test_info->verbosity >= VERBOSITY_ADDITIONAL){ + fprintf(test_info->errout, + "[%u] %s, ", + async_queries[i].done_order, + async_queries[i].name); + } + } + + if (async_queries[NQUERIES - 1].done_order == NQUERIES) { + fputs("Queries are not concurrent", test_info->errout); + return EXIT_CRITICAL; + } else { + fputs("Queries are concurrent", test_info->errout); + return EXIT_OK; + } +} + static struct test_funcs_s { const char *name; @@ -1474,6 +1603,7 @@ static struct test_funcs_s { "keepalive", false, true, test_keepalive }, { "dnssec-validate", false, false, test_dnssec_validate }, { "tls-1.3", true, false, test_tls13 }, + { "concurrent", false, true, test_concurrent }, { NULL, false, false, NULL } }; From 1e774a95f51b98bd9a45c29a7933137ee4e0406a Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 22 Jan 2018 10:23:29 +0000 Subject: [PATCH 34/47] Don't rely on GCC extensions. --- src/tools/getdns_server_mon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 2f8cd182..47401492 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -1494,7 +1494,7 @@ static exit_value test_concurrent(const struct test_info_s *test_info, } /* Three names with random TLDs. */ - const int NAMELEN = 30; + #define NAMELEN 30 char names[3][NAMELEN + 1]; srandom((unsigned int) time(NULL)); From bedd3a02cf7c44fa5173e833216d04c98857f9f1 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 22 Jan 2018 14:43:37 +0000 Subject: [PATCH 35/47] Revise concurrency test to use .delay.getdnsapi.net. This gives more secure results than the previous method. --- src/tools/getdns_server_mon.c | 87 ++++++++++++++++------------------- 1 file changed, 40 insertions(+), 47 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 47401492..f4f39a31 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -1482,38 +1482,36 @@ static void concurrent_callback(getdns_context *context, query->done_order = ++callback_no; query->error = (callback_type != GETDNS_CALLBACK_COMPLETE); -} -static exit_value test_concurrent(const struct test_info_s *test_info, - char ** av) -{ - if (*av) { - fputs("concurrent takes no arguments", - test_info->errout); - return EXIT_USAGE; + if (test_info.verbosity >= VERBOSITY_ADDITIONAL) { + if (test_info.monitoring) { + snprintcat(test_info.perf_output, + MAX_PERF_OUTPUT_LEN, + "->%s;", + query->name); + } else { + printf("Result:\t\t\t%s", query->name); + if (query->error) + printf(" (callback %d)\n", callback_type); + else + putchar('\n'); + } } - /* Three names with random TLDs. */ - #define NAMELEN 30 - char names[3][NAMELEN + 1]; +} - srandom((unsigned int) time(NULL)); - - for (int i = 0; i < 3; ++i) { - const char ROOT[] = "getdnsapi.dx"; - strcpy(names[i], ROOT); - for (int j = sizeof(ROOT) - 1; j < NAMELEN; ++j) { - names[i][j] = 'a' + random() % ('z' - 'a'); - } - names[i][NAMELEN] = '\0'; +static exit_value test_concurrent(struct test_info_s *test_info, + char ** av) +{ + if (*av) { + strcpy(test_info->base_output, "concurrent takes no arguments"); + return EXIT_USAGE; } /* A set of asynchronous queries to send. One exists. */ const char GOOD_NAME[] = "getdnsapi.net"; struct async_query async_queries[] = { - { names[0], 0, false }, - { names[1], 0, false }, - { names[2], 0, false }, + { "1000.delay.getdnsapi.net", 0, false }, { GOOD_NAME, 0, false } }; unsigned NQUERIES = sizeof(async_queries) / sizeof(async_queries[0]); @@ -1532,12 +1530,20 @@ static exit_value test_concurrent(const struct test_info_s *test_info, return xit; /* Now launch async searches for all the above. */ - if (test_info->verbosity >= VERBOSITY_ADDITIONAL){ - fputs("concurrent search: ", test_info->errout); - } for (unsigned i = 0; i < NQUERIES; ++i) { getdns_return_t ret; + if (test_info->verbosity >= VERBOSITY_ADDITIONAL) { + if (test_info->monitoring) { + snprintcat(test_info->perf_output, + MAX_PERF_OUTPUT_LEN, + "%s->;", + async_queries[i].name); + } else { + printf("Search:\t\t\t%s\n", async_queries[i].name); + } + } + if ((ret = getdns_general(test_info->context, async_queries[i].name, GETDNS_RRTYPE_AAAA, @@ -1545,43 +1551,30 @@ static exit_value test_concurrent(const struct test_info_s *test_info, &async_queries[i], NULL, concurrent_callback)) != GETDNS_RETURN_GOOD) { - fprintf(test_info->errout, - "Async search failed: %s (%d)", - getdns_get_errorstr_by_id(ret), - ret); + snprintf(test_info->base_output, + MAX_BASE_OUTPUT_LEN, + "Async search failed: %s (%d)", + getdns_get_errorstr_by_id(ret), + ret); return EXIT_UNKNOWN; } - if (test_info->verbosity >= VERBOSITY_ADDITIONAL){ - fprintf(test_info->errout, - "[%u] %s, ", - i, async_queries[i].name); - } } /* And wait for them to complete. */ getdns_context_run(test_info->context); - if (test_info->verbosity >= VERBOSITY_ADDITIONAL){ - fputs("concurrent result: ", test_info->errout); - } for (unsigned i = 0; i < NQUERIES; ++i) { if (async_queries[i].error) { - fputs("Query did not complete", test_info->errout); + strcpy(test_info->base_output, "Query did not complete"); return EXIT_UNKNOWN; } - if (test_info->verbosity >= VERBOSITY_ADDITIONAL){ - fprintf(test_info->errout, - "[%u] %s, ", - async_queries[i].done_order, - async_queries[i].name); - } } if (async_queries[NQUERIES - 1].done_order == NQUERIES) { - fputs("Queries are not concurrent", test_info->errout); + strcpy(test_info->base_output, "Queries are not concurrent"); return EXIT_CRITICAL; } else { - fputs("Queries are concurrent", test_info->errout); + strcpy(test_info->base_output, "Queries are concurrent"); return EXIT_OK; } } From 7e884e2cd0b23bec8a592a1aaf7ea07f9af40dd2 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 23 Jan 2018 11:30:12 +0000 Subject: [PATCH 36/47] Rename concurrent to OOOR (Out Of Order Responses). --- src/tools/getdns_server_mon.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index f4f39a31..78f05f53 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -305,12 +305,12 @@ static void usage() "tsig spec: [:]:\n" "\n" "Tests:\n" -" concurrent Check server support for concurrent query\n" -" processing\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" +" OOOR Check whether server delivers responses out of\n" +" query order on a TCP or TLS connection\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" @@ -1466,11 +1466,11 @@ struct async_query bool error; }; -static void concurrent_callback(getdns_context *context, - getdns_callback_type_t callback_type, - getdns_dict *response, - void *userarg, - getdns_transaction_t transaction_id) +static void out_of_order_callback(getdns_context *context, + getdns_callback_type_t callback_type, + getdns_dict *response, + void *userarg, + getdns_transaction_t transaction_id) { (void) context; (void) callback_type; @@ -1500,11 +1500,11 @@ static void concurrent_callback(getdns_context *context, } -static exit_value test_concurrent(struct test_info_s *test_info, - char ** av) +static exit_value test_out_of_order(struct test_info_s *test_info, + char ** av) { if (*av) { - strcpy(test_info->base_output, "concurrent takes no arguments"); + strcpy(test_info->base_output, "OOOR takes no arguments"); return EXIT_USAGE; } @@ -1550,7 +1550,7 @@ static exit_value test_concurrent(struct test_info_s *test_info, NULL, &async_queries[i], NULL, - concurrent_callback)) != GETDNS_RETURN_GOOD) { + out_of_order_callback)) != GETDNS_RETURN_GOOD) { snprintf(test_info->base_output, MAX_BASE_OUTPUT_LEN, "Async search failed: %s (%d)", @@ -1571,10 +1571,10 @@ static exit_value test_concurrent(struct test_info_s *test_info, } if (async_queries[NQUERIES - 1].done_order == NQUERIES) { - strcpy(test_info->base_output, "Queries are not concurrent"); + strcpy(test_info->base_output, "Responses are in query order"); return EXIT_CRITICAL; } else { - strcpy(test_info->base_output, "Queries are concurrent"); + strcpy(test_info->base_output, "Responses are not in query order"); return EXIT_OK; } } @@ -1596,7 +1596,7 @@ static struct test_funcs_s { "keepalive", false, true, test_keepalive }, { "dnssec-validate", false, false, test_dnssec_validate }, { "tls-1.3", true, false, test_tls13 }, - { "concurrent", false, true, test_concurrent }, + { "OOOR", false, true, test_out_of_order }, { NULL, false, false, NULL } }; From a4f17760abdf6c3f64d7d92fa7d9528ef6f8d5c8 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 23 Jan 2018 12:13:59 +0000 Subject: [PATCH 37/47] Revise rcode_text() to get text from getdns, and add rrtype_text(). --- src/tools/getdns_server_mon.c | 55 +++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 78f05f53..5cb05606 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -116,7 +116,7 @@ static void snprintcat(char *buf, size_t buflen, const char *format, ...) static int get_rrtype(const char *t) { - char buf[1024] = "GETDNS_RRTYPE_"; + char buf[128] = "GETDNS_RRTYPE_"; uint32_t rrtype; long int l; size_t i; @@ -139,21 +139,45 @@ static int get_rrtype(const char *t) return -1; } +static const char *getdns_intval_text(int val, const char *name, const char *prefix) +{ + getdns_dict *d = getdns_dict_create(); + char buf[128]; + static char res[20]; + + if (getdns_dict_set_int(d, name, val) != GETDNS_RETURN_GOOD) + goto err; + + getdns_pretty_snprint_dict(buf, sizeof(buf), d); + + const char *p = strstr(buf, prefix); + + if (!p) + goto err; + p += strlen(prefix); + + char *q = res; + + while (*p && *p != '\n') + *q++ = *p++; + + getdns_dict_destroy(d); + return res; + +err: + getdns_dict_destroy(d); + snprintf(res, sizeof(res), "%d", val); + return res; +} + +static const char *rrtype_text(int rrtype) +{ + return getdns_intval_text(rrtype, "type", "GETDNS_RRTYPE_"); +} + static const char *rcode_text(int rcode) { - const char *text[] = { - "OK", - "FORMERR", - "SERVFAIL", - "NXDOMAIN", - "NOTIMP", - "REFUSED" - }; - - if ((size_t) rcode >= sizeof(text) / sizeof(text[0])) - return "(?)"; - else - return text[rcode]; + return getdns_intval_text(rcode, "rcode", "GETDNS_RCODE_"); } #if OPENSSL_VERSION_NUMBER < 0x10002000 @@ -441,7 +465,8 @@ static exit_value search(struct test_info_s *test_info, if (test_info->verbosity >= VERBOSITY_ADDITIONAL && !test_info->monitoring) { - printf("Lookup:\t\t\t%s %u\n", name, type); + printf("DNS Lookup name:\t%s\n", name); + printf("DNS Lookup RR type:\t%s\n", rrtype_text(type)); } if (test_info->debug_output) { From f3b2f838797666f04be62d5801c87cc6cd4657bd Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 23 Jan 2018 12:14:40 +0000 Subject: [PATCH 38/47] More output tittivating. Make verbose by default in non-monitoring mode. --- src/tools/getdns_server_mon.c | 57 +++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 5cb05606..f79a95ce 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -568,10 +568,10 @@ static exit_value check_result(struct test_info_s *test_info, if (test_info->monitoring) { snprintcat(test_info->perf_output, MAX_PERF_OUTPUT_LEN, - "result=%d;", + "getdns=%d;", error_id); } else { - printf("DNS result:\t\t%s (%d)\n", + printf("getdns result:\t\t%s (%d)\n", getdns_get_errorstr_by_id(error_id), error_id); } @@ -721,6 +721,55 @@ static exit_value get_report_info(struct test_info_s *test_info, if (cert_expire_time) *cert_expire_time = cert_expire_time_val; + getdns_bindata *tls_version; + + if (getdns_dict_get_bindata(d, "tls_version", &tls_version) == GETDNS_RETURN_GOOD) { + const char *version_text = (char *) tls_version->data; + + if (test_info->verbosity >= VERBOSITY_ADDITIONAL) { + if (test_info->monitoring) { + snprintcat(test_info->perf_output, + MAX_PERF_OUTPUT_LEN, + "tls=%s;", + version_text); + } else { + printf("TLS version:\t\t%s\n", version_text); + } + } + + /* + * This is always in the context, so show only if we got + * TLS info in the call reporting. + */ + uint32_t tls_auth; + getdns_dict *context_dict = getdns_context_get_api_information(test_info->context); + + if (getdns_dict_get_int(context_dict, "/all_context/tls_authentication", &tls_auth) == GETDNS_RETURN_GOOD) { + const char *auth_text = "???"; + + switch(tls_auth) { + case GETDNS_AUTHENTICATION_NONE: + auth_text = "Opportunistic"; + break; + + case GETDNS_AUTHENTICATION_REQUIRED: + auth_text = "Strict"; + break; + } + + if (test_info->verbosity >= VERBOSITY_ADDITIONAL) { + if (test_info->monitoring) { + snprintcat(test_info->perf_output, + MAX_PERF_OUTPUT_LEN, + "%s;", + auth_text); + } else { + printf("TLS authentication:\t%s\n", auth_text); + } + } + } + } + return EXIT_OK; } @@ -1718,6 +1767,10 @@ int main(int ac, char *av[]) if (*av == NULL || *av[0] != '@') usage(); + /* Non-monitoring output is chatty by default. */ + if (!test_info.monitoring) + ++test_info.verbosity; + const char *upstream = *av++; getdns_dict *resolver; getdns_bindata *address; From fcaa4f9845ba9d6cdae43fd7e36ce66f10c0b37a Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 23 Jan 2018 12:37:14 +0000 Subject: [PATCH 39/47] Reflow usage message entry. --- src/tools/getdns_server_mon.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index f79a95ce..904bd290 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -331,7 +331,8 @@ static void usage() "Tests:\n" " lookup [ []] Check lookup on server\n" " keepalive [ []]\n" -" Check server support for EDNS0 keepalive in TCP/TLS\n" +" Check server support for EDNS0 keepalive in\n" +" TCP or TLS connections\n" " Timeout of 0 is off.\n" " OOOR Check whether server delivers responses out of\n" " query order on a TCP or TLS connection\n" From 8ba53f10b6a5c8985805aace9790ed6e3b423dcf Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 23 Jan 2018 13:31:16 +0000 Subject: [PATCH 40/47] Correct RTT warning and critical default thresholds. --- src/tools/getdns_server_mon.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tools/getdns_server_mon.c b/src/tools/getdns_server_mon.c index 904bd290..7a2f74ef 100644 --- a/src/tools/getdns_server_mon.c +++ b/src/tools/getdns_server_mon.c @@ -44,8 +44,8 @@ #define APP_NAME "getdns_server_mon" -#define RTT_CRITICAL_MS 250 -#define RTT_WARNING_MS 500 +#define RTT_CRITICAL_MS 500 +#define RTT_WARNING_MS 250 #define CERT_EXPIRY_CRITICAL_DAYS 7 #define CERT_EXPIRY_WARNING_DAYS 14 @@ -338,7 +338,8 @@ static void usage() " query order on a TCP or TLS connection\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" +" Check if server round trip time exceeds\n" +" thresholds (default 250,500)\n" "\n" " dnssec-validate Check whether server does DNSSEC validation\n" "\n" From b0661b9d9fba60ceee4b7cd93cc5d61d7d23a955 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Tue, 23 Jan 2018 13:45:55 +0000 Subject: [PATCH 41/47] Add a tool README. Use AsciiDoc for this, as the GitHub table support in Markdown is woeful. But AsciiDoc is always better than Markdown anyway. --- src/tools/README.adoc | 261 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 src/tools/README.adoc diff --git a/src/tools/README.adoc b/src/tools/README.adoc new file mode 100644 index 00000000..b8641099 --- /dev/null +++ b/src/tools/README.adoc @@ -0,0 +1,261 @@ += getdns tools + +This directory contains some tools based on `getdns`. + +* `getdns_query` - a command line wrapper for `getdns`. +* `getdns_server_mon` - test DNS server function and capabilities. + +== `getdns_query` + +`getdns_query` is a command line wrapper for `getdns` exposing the +features of this implementation (both in the official API and the +additional API functions). + +=== Usage + +---- +usage: getdns_query [