diff options
Diffstat (limited to '')
-rw-r--r-- | lib/samples/nsprobe.c | 1216 |
1 files changed, 1216 insertions, 0 deletions
diff --git a/lib/samples/nsprobe.c b/lib/samples/nsprobe.c new file mode 100644 index 0000000..2ebb19a --- /dev/null +++ b/lib/samples/nsprobe.c @@ -0,0 +1,1216 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + + +#include <config.h> + +#ifndef WIN32 +#include <sys/types.h> +#include <sys/socket.h> + +#include <unistd.h> +#include <netdb.h> +#endif + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <isc/app.h> +#include <isc/buffer.h> +#include <isc/commandline.h> +#include <isc/lib.h> +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/socket.h> +#include <isc/sockaddr.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/timer.h> +#include <isc/util.h> + +#include <dns/client.h> +#include <dns/fixedname.h> +#include <dns/lib.h> +#include <dns/message.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rdataset.h> +#include <dns/rdatastruct.h> +#include <dns/rdatatype.h> +#include <dns/result.h> + +#define MAX_PROBES 1000 + +static dns_client_t *client = NULL; +static isc_task_t *probe_task = NULL; +static isc_appctx_t *actx = NULL; +static isc_mem_t *mctx = NULL; +static unsigned int outstanding_probes = 0; +const char *cacheserver = "127.0.0.1"; +static FILE *input; + +typedef enum { + none, + exist, + nxdomain, + othererr, + multiplesoa, + multiplecname, + brokenanswer, + lame, + timedout, + notype, + unexpected +} query_result_t; + +struct server { + ISC_LINK(struct server) link; + + isc_sockaddr_t address; + query_result_t result_a; + query_result_t result_aaaa; +}; + +struct probe_ns { + ISC_LINK(struct probe_ns) link; + + dns_fixedname_t fixedname; + dns_name_t *name; + struct server *current_server; + ISC_LIST(struct server) servers; +}; + +struct probe_trans { + bool inuse; + char *domain; + dns_fixedname_t fixedname; + dns_name_t *qname; + const char **qlabel; + bool qname_found; + dns_clientrestrans_t *resid; + dns_message_t *qmessage; + dns_message_t *rmessage; + dns_clientreqtrans_t *reqid; + + /* NS list */ + struct probe_ns *current_ns; + ISC_LIST(struct probe_ns) nslist; +}; + +struct lcl_stat { + unsigned long valid; + unsigned long ignore; + unsigned long nxdomain; + unsigned long othererr; + unsigned long multiplesoa; + unsigned long multiplecname; + unsigned long brokenanswer; + unsigned long lame; + unsigned long unknown; +} server_stat, domain_stat; + +static unsigned long number_of_domains = 0; +static unsigned long number_of_servers = 0; +static unsigned long multiple_error_domains = 0; +static bool debug_mode = false; +static int verbose_level = 0; +static const char *qlabels[] = {"www.", "ftp.", NULL}; +static struct probe_trans probes[MAX_PROBES]; + +static isc_result_t probe_domain(struct probe_trans *trans); +static void reset_probe(struct probe_trans *trans); +static isc_result_t fetch_nsaddress(struct probe_trans *trans); +static isc_result_t probe_name(struct probe_trans *trans, + dns_rdatatype_t type); + +/* Dump an rdataset for debug */ +static isc_result_t +print_rdataset(dns_rdataset_t *rdataset, dns_name_t *owner) { + isc_buffer_t target; + isc_result_t result; + isc_region_t r; + char t[4096]; + + if (!debug_mode) + return (ISC_R_SUCCESS); + + isc_buffer_init(&target, t, sizeof(t)); + + if (!dns_rdataset_isassociated(rdataset)) + return (ISC_R_SUCCESS); + result = dns_rdataset_totext(rdataset, owner, false, false, + &target); + if (result != ISC_R_SUCCESS) + return (result); + isc_buffer_usedregion(&target, &r); + printf("%.*s", (int)r.length, (char *)r.base); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +print_name(dns_name_t *name) { + isc_result_t result; + isc_buffer_t target; + isc_region_t r; + char t[4096]; + + isc_buffer_init(&target, t, sizeof(t)); + result = dns_name_totext(name, true, &target); + if (result == ISC_R_SUCCESS) { + isc_buffer_usedregion(&target, &r); + printf("%.*s", (int)r.length, (char *)r.base); + } else + printf("(invalid name)"); + + return (result); +} + +static isc_result_t +print_address(FILE *fp, isc_sockaddr_t *addr) { + char buf[NI_MAXHOST]; + + if (getnameinfo(&addr->type.sa, addr->length, buf, sizeof(buf), + NULL, 0, NI_NUMERICHOST) == 0) { + fprintf(fp, "%s", buf); + } else { + fprintf(fp, "(invalid address)"); + } + + return (ISC_R_SUCCESS); +} + +static void +ctxs_destroy(isc_mem_t **mctxp, isc_appctx_t **actxp, + isc_taskmgr_t **taskmgrp, isc_socketmgr_t **socketmgrp, + isc_timermgr_t **timermgrp) +{ + if (*taskmgrp != NULL) + isc_taskmgr_destroy(taskmgrp); + + if (*timermgrp != NULL) + isc_timermgr_destroy(timermgrp); + + if (*socketmgrp != NULL) + isc_socketmgr_destroy(socketmgrp); + + if (*actxp != NULL) + isc_appctx_destroy(actxp); + + if (*mctxp != NULL) + isc_mem_destroy(mctxp); +} + +static isc_result_t +ctxs_init(isc_mem_t **mctxp, isc_appctx_t **actxp, + isc_taskmgr_t **taskmgrp, isc_socketmgr_t **socketmgrp, + isc_timermgr_t **timermgrp) +{ + isc_result_t result; + + result = isc_mem_create(0, 0, mctxp); + if (result != ISC_R_SUCCESS) + goto fail; + + result = isc_appctx_create(*mctxp, actxp); + if (result != ISC_R_SUCCESS) + goto fail; + + result = isc_taskmgr_createinctx(*mctxp, *actxp, 1, 0, taskmgrp); + if (result != ISC_R_SUCCESS) + goto fail; + + result = isc_socketmgr_createinctx(*mctxp, *actxp, socketmgrp); + if (result != ISC_R_SUCCESS) + goto fail; + + result = isc_timermgr_createinctx(*mctxp, *actxp, timermgrp); + if (result != ISC_R_SUCCESS) + goto fail; + + return (ISC_R_SUCCESS); + + fail: + ctxs_destroy(mctxp, actxp, taskmgrp, socketmgrp, timermgrp); + + return (result); +} + +/* + * Common routine to make query data + */ +static isc_result_t +make_querymessage(dns_message_t *message, dns_name_t *qname0, + dns_rdatatype_t rdtype) +{ + dns_name_t *qname = NULL; + dns_rdataset_t *qrdataset = NULL; + isc_result_t result; + + message->opcode = dns_opcode_query; + message->rdclass = dns_rdataclass_in; + + result = dns_message_gettempname(message, &qname); + if (result != ISC_R_SUCCESS) + goto cleanup; + + result = dns_message_gettemprdataset(message, &qrdataset); + if (result != ISC_R_SUCCESS) + goto cleanup; + + dns_name_init(qname, NULL); + dns_name_clone(qname0, qname); + dns_rdataset_makequestion(qrdataset, message->rdclass, rdtype); + ISC_LIST_APPEND(qname->list, qrdataset, link); + dns_message_addname(message, qname, DNS_SECTION_QUESTION); + + return (ISC_R_SUCCESS); + + cleanup: + if (qname != NULL) + dns_message_puttempname(message, &qname); + if (qrdataset != NULL) + dns_message_puttemprdataset(message, &qrdataset); + return (result); +} + +/* + * Update statistics + */ +static inline void +increment_entry(unsigned long *entryp) { + (*entryp)++; + INSIST(*entryp != 0U); /* check overflow */ +} + +static void +update_stat(struct probe_trans *trans) { + struct probe_ns *pns; + struct server *server; + struct lcl_stat local_stat; + unsigned int err_count = 0; + const char *stattype; + + increment_entry(&number_of_domains); + memset(&local_stat, 0, sizeof(local_stat)); + + /* Update per sever statistics */ + for (pns = ISC_LIST_HEAD(trans->nslist); pns != NULL; + pns = ISC_LIST_NEXT(pns, link)) { + for (server = ISC_LIST_HEAD(pns->servers); server != NULL; + server = ISC_LIST_NEXT(server, link)) { + increment_entry(&number_of_servers); + + if (server->result_aaaa == exist || + server->result_aaaa == notype) { + /* + * Don't care about the result of A query if + * the answer to AAAA query was expected. + */ + stattype = "valid"; + increment_entry(&server_stat.valid); + increment_entry(&local_stat.valid); + } else if (server->result_a == exist) { + switch (server->result_aaaa) { + case exist: + case notype: + stattype = "valid"; + increment_entry(&server_stat.valid); + increment_entry(&local_stat.valid); + break; + case timedout: + stattype = "ignore"; + increment_entry(&server_stat.ignore); + increment_entry(&local_stat.ignore); + break; + case nxdomain: + stattype = "nxdomain"; + increment_entry(&server_stat.nxdomain); + increment_entry(&local_stat.nxdomain); + break; + case othererr: + stattype = "othererr"; + increment_entry(&server_stat.othererr); + increment_entry(&local_stat.othererr); + break; + case multiplesoa: + stattype = "multiplesoa"; + increment_entry(&server_stat.multiplesoa); + increment_entry(&local_stat.multiplesoa); + break; + case multiplecname: + stattype = "multiplecname"; + increment_entry(&server_stat.multiplecname); + increment_entry(&local_stat.multiplecname); + break; + case brokenanswer: + stattype = "brokenanswer"; + increment_entry(&server_stat.brokenanswer); + increment_entry(&local_stat.brokenanswer); + break; + case lame: + stattype = "lame"; + increment_entry(&server_stat.lame); + increment_entry(&local_stat.lame); + break; + default: + stattype = "unknown"; + increment_entry(&server_stat.unknown); + increment_entry(&local_stat.unknown); + break; + } + } else { + stattype = "unknown"; + increment_entry(&server_stat.unknown); + increment_entry(&local_stat.unknown); + } + + if (verbose_level > 1 || + (verbose_level == 1 && + strcmp(stattype, "valid") != 0 && + strcmp(stattype, "unknown") != 0)) { + print_name(pns->name); + putchar('('); + print_address(stdout, &server->address); + printf(") for %s:%s\n", trans->domain, + stattype); + } + } + } + + /* Update per domain statistics */ + if (local_stat.ignore > 0U) { + if (verbose_level > 0) + printf("%s:ignore\n", trans->domain); + increment_entry(&domain_stat.ignore); + err_count++; + } + if (local_stat.nxdomain > 0U) { + if (verbose_level > 0) + printf("%s:nxdomain\n", trans->domain); + increment_entry(&domain_stat.nxdomain); + err_count++; + } + if (local_stat.othererr > 0U) { + if (verbose_level > 0) + printf("%s:othererr\n", trans->domain); + increment_entry(&domain_stat.othererr); + err_count++; + } + if (local_stat.multiplesoa > 0U) { + if (verbose_level > 0) + printf("%s:multiplesoa\n", trans->domain); + increment_entry(&domain_stat.multiplesoa); + err_count++; + } + if (local_stat.multiplecname > 0U) { + if (verbose_level > 0) + printf("%s:multiplecname\n", trans->domain); + increment_entry(&domain_stat.multiplecname); + err_count++; + } + if (local_stat.brokenanswer > 0U) { + if (verbose_level > 0) + printf("%s:brokenanswer\n", trans->domain); + increment_entry(&domain_stat.brokenanswer); + err_count++; + } + if (local_stat.lame > 0U) { + if (verbose_level > 0) + printf("%s:lame\n", trans->domain); + increment_entry(&domain_stat.lame); + err_count++; + } + + if (err_count > 1U) + increment_entry(&multiple_error_domains); + + /* + * We regard the domain as valid if and only if no authoritative server + * has a problem and at least one server is known to be valid. + */ + if (local_stat.valid > 0U && err_count == 0U) { + if (verbose_level > 1) + printf("%s:valid\n", trans->domain); + increment_entry(&domain_stat.valid); + } + + /* + * If the domain has no available server or all servers have the + * 'unknown' result, the domain's result is also regarded as unknown. + */ + if (local_stat.valid == 0U && err_count == 0U) { + if (verbose_level > 1) + printf("%s:unknown\n", trans->domain); + increment_entry(&domain_stat.unknown); + } +} + +/* + * Search for an existent name with an A RR + */ + +static isc_result_t +set_nextqname(struct probe_trans *trans) { + isc_result_t result; + unsigned int domainlen; + isc_buffer_t b; + char buf[4096]; /* XXX ad-hoc constant, but should be enough */ + + if (*trans->qlabel == NULL) + return (ISC_R_NOMORE); + + result = isc_string_copy(buf, sizeof(buf), *trans->qlabel); + if (result != ISC_R_SUCCESS) + return (result); + result = isc_string_append(buf, sizeof(buf), trans->domain); + if (result != ISC_R_SUCCESS) + return (result); + + domainlen = strlen(buf); + isc_buffer_init(&b, buf, domainlen); + isc_buffer_add(&b, domainlen); + trans->qname = dns_fixedname_initname(&trans->fixedname); + result = dns_name_fromtext(trans->qname, &b, dns_rootname, + 0, NULL); + + trans->qlabel++; + + return (result); +} + +static void +request_done(isc_task_t *task, isc_event_t *event) { + struct probe_trans *trans = event->ev_arg; + dns_clientreqevent_t *rev = (dns_clientreqevent_t *)event; + dns_message_t *rmessage; + struct probe_ns *pns; + struct server *server; + isc_result_t result; + query_result_t *resultp; + dns_name_t *name; + dns_rdataset_t *rdataset; + dns_rdatatype_t type; + + REQUIRE(task == probe_task); + REQUIRE(trans != NULL && trans->inuse == true); + rmessage = rev->rmessage; + REQUIRE(rmessage == trans->rmessage); + INSIST(outstanding_probes > 0); + + server = trans->current_ns->current_server; + INSIST(server != NULL); + + if (server->result_a == none) { + type = dns_rdatatype_a; + resultp = &server->result_a; + } else { + resultp = &server->result_aaaa; + type = dns_rdatatype_aaaa; + } + + if (rev->result == ISC_R_SUCCESS) { + if ((rmessage->flags & DNS_MESSAGEFLAG_AA) == 0) + *resultp = lame; + else if (rmessage->rcode == dns_rcode_nxdomain) + *resultp = nxdomain; + else if (rmessage->rcode != dns_rcode_noerror) + *resultp = othererr; + else if (rmessage->counts[DNS_SECTION_ANSWER] == 0) { + /* no error but empty answer */ + *resultp = notype; + } else { + result = dns_message_firstname(rmessage, + DNS_SECTION_ANSWER); + while (result == ISC_R_SUCCESS) { + name = NULL; + dns_message_currentname(rmessage, + DNS_SECTION_ANSWER, + &name); + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, + link)) { + (void)print_rdataset(rdataset, name); + + if (rdataset->type == + dns_rdatatype_cname || + rdataset->type == + dns_rdatatype_dname) { + /* Should chase the chain? */ + *resultp = exist; + goto found; + } else if (rdataset->type == type) { + *resultp = exist; + goto found; + } + } + result = dns_message_nextname(rmessage, + DNS_SECTION_ANSWER); + } + + /* + * Something unexpected happened: the response + * contained a non-empty authoritative answer, but we + * could not find an expected result. + */ + *resultp = unexpected; + } + } else if (rev->result == DNS_R_RECOVERABLE || + rev->result == DNS_R_BADLABELTYPE) { + /* Broken response. Try identifying known cases. */ + *resultp = brokenanswer; + + if (rmessage->counts[DNS_SECTION_ANSWER] > 0) { + result = dns_message_firstname(rmessage, + DNS_SECTION_ANSWER); + while (result == ISC_R_SUCCESS) { + /* + * Check to see if the response has multiple + * CNAME RRs. Update the result code if so. + */ + name = NULL; + dns_message_currentname(rmessage, + DNS_SECTION_ANSWER, + &name); + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, + link)) { + if (rdataset->type == + dns_rdatatype_cname && + dns_rdataset_count(rdataset) > 1) { + *resultp = multiplecname; + goto found; + } + } + result = dns_message_nextname(rmessage, + DNS_SECTION_ANSWER); + } + } + + if (rmessage->counts[DNS_SECTION_AUTHORITY] > 0) { + result = dns_message_firstname(rmessage, + DNS_SECTION_AUTHORITY); + while (result == ISC_R_SUCCESS) { + /* + * Check to see if the response has multiple + * SOA RRs. Update the result code if so. + */ + name = NULL; + dns_message_currentname(rmessage, + DNS_SECTION_AUTHORITY, + &name); + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, + link)) { + if (rdataset->type == + dns_rdatatype_soa && + dns_rdataset_count(rdataset) > 1) { + *resultp = multiplesoa; + goto found; + } + } + result = dns_message_nextname(rmessage, + DNS_SECTION_AUTHORITY); + } + } + } else if (rev->result == ISC_R_TIMEDOUT) + *resultp = timedout; + else { + fprintf(stderr, "unexpected result: %u (domain=%s, server=", + rev->result, trans->domain); + print_address(stderr, &server->address); + fputc('\n', stderr); + *resultp = unexpected; + } + + found: + INSIST(*resultp != none); + if (type == dns_rdatatype_a && *resultp == exist) + trans->qname_found = true; + + dns_client_destroyreqtrans(&trans->reqid); + isc_event_free(&event); + dns_message_reset(trans->rmessage, DNS_MESSAGE_INTENTPARSE); + + result = probe_name(trans, type); + if (result == ISC_R_NOMORE) { + /* We've tried all addresses of all servers. */ + if (type == dns_rdatatype_a && trans->qname_found) { + /* + * If we've explored A RRs and found an existent + * record, we can move to AAAA. + */ + trans->current_ns = ISC_LIST_HEAD(trans->nslist); + probe_name(trans, dns_rdatatype_aaaa); + result = ISC_R_SUCCESS; + } else if (type == dns_rdatatype_a) { + /* + * No server provided an existent A RR of this name. + * Try next label. + */ + dns_fixedname_invalidate(&trans->fixedname); + trans->qname = NULL; + result = set_nextqname(trans); + if (result == ISC_R_SUCCESS) { + trans->current_ns = + ISC_LIST_HEAD(trans->nslist); + for (pns = trans->current_ns; pns != NULL; + pns = ISC_LIST_NEXT(pns, link)) { + for (server = ISC_LIST_HEAD(pns->servers); + server != NULL; + server = ISC_LIST_NEXT(server, + link)) { + INSIST(server->result_aaaa == + none); + server->result_a = none; + } + } + result = probe_name(trans, dns_rdatatype_a); + } + } + if (result != ISC_R_SUCCESS) { + /* + * We've explored AAAA RRs or failed to find a valid + * query label. Wrap up the result and move to the + * next domain. + */ + reset_probe(trans); + } + } else if (result != ISC_R_SUCCESS) + reset_probe(trans); /* XXX */ +} + +static isc_result_t +probe_name(struct probe_trans *trans, dns_rdatatype_t type) { + isc_result_t result; + struct probe_ns *pns; + struct server *server; + + REQUIRE(trans->reqid == NULL); + REQUIRE(type == dns_rdatatype_a || type == dns_rdatatype_aaaa); + + for (pns = trans->current_ns; pns != NULL; + pns = ISC_LIST_NEXT(pns, link)) { + for (server = ISC_LIST_HEAD(pns->servers); server != NULL; + server = ISC_LIST_NEXT(server, link)) { + if ((type == dns_rdatatype_a && + server->result_a == none) || + (type == dns_rdatatype_aaaa && + server->result_aaaa == none)) { + pns->current_server = server; + goto found; + } + } + } + + found: + trans->current_ns = pns; + if (pns == NULL) + return (ISC_R_NOMORE); + + INSIST(pns->current_server != NULL); + dns_message_reset(trans->qmessage, DNS_MESSAGE_INTENTRENDER); + result = make_querymessage(trans->qmessage, trans->qname, type); + if (result != ISC_R_SUCCESS) + return (result); + result = dns_client_startrequest(client, trans->qmessage, + trans->rmessage, + &pns->current_server->address, + 0, DNS_MESSAGEPARSE_BESTEFFORT, + NULL, 120, 0, 4, + probe_task, request_done, trans, + &trans->reqid); + + return (result); +} + +/* + * Get IP addresses of NSes + */ + +static void +resolve_nsaddress(isc_task_t *task, isc_event_t *event) { + struct probe_trans *trans = event->ev_arg; + dns_clientresevent_t *rev = (dns_clientresevent_t *)event; + dns_name_t *name; + dns_rdataset_t *rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + struct probe_ns *pns = trans->current_ns; + isc_result_t result; + + REQUIRE(task == probe_task); + REQUIRE(trans->inuse == true); + REQUIRE(pns != NULL); + INSIST(outstanding_probes > 0); + + for (name = ISC_LIST_HEAD(rev->answerlist); name != NULL; + name = ISC_LIST_NEXT(name, link)) { + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) { + (void)print_rdataset(rdataset, name); + + if (rdataset->type != dns_rdatatype_a) + continue; + + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) { + dns_rdata_in_a_t rdata_a; + struct server *server; + + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &rdata_a, + NULL); + if (result != ISC_R_SUCCESS) + continue; + + server = isc_mem_get(mctx, sizeof(*server)); + if (server == NULL) { + fprintf(stderr, "resolve_nsaddress: " + "mem_get failed"); + result = ISC_R_NOMEMORY; + POST(result); + goto cleanup; + } + isc_sockaddr_fromin(&server->address, + &rdata_a.in_addr, 53); + ISC_LINK_INIT(server, link); + server->result_a = none; + server->result_aaaa = none; + ISC_LIST_APPEND(pns->servers, server, link); + } + } + } + + cleanup: + dns_client_freeresanswer(client, &rev->answerlist); + dns_client_destroyrestrans(&trans->resid); + isc_event_free(&event); + + next_ns: + trans->current_ns = ISC_LIST_NEXT(pns, link); + if (trans->current_ns == NULL) { + trans->current_ns = ISC_LIST_HEAD(trans->nslist); + dns_fixedname_invalidate(&trans->fixedname); + trans->qname = NULL; + result = set_nextqname(trans); + if (result == ISC_R_SUCCESS) + result = probe_name(trans, dns_rdatatype_a); + } else { + result = fetch_nsaddress(trans); + if (result != ISC_R_SUCCESS) + goto next_ns; /* XXX: this is unlikely to succeed */ + } + + if (result != ISC_R_SUCCESS) + reset_probe(trans); +} + +static isc_result_t +fetch_nsaddress(struct probe_trans *trans) { + struct probe_ns *pns; + + pns = trans->current_ns; + REQUIRE(pns != NULL); + + return (dns_client_startresolve(client, pns->name, dns_rdataclass_in, + dns_rdatatype_a, 0, probe_task, + resolve_nsaddress, trans, + &trans->resid)); +} + +/* + * Get NS RRset for a given domain + */ + +static void +reset_probe(struct probe_trans *trans) { + struct probe_ns *pns; + struct server *server; + isc_result_t result; + + REQUIRE(trans->resid == NULL); + REQUIRE(trans->reqid == NULL); + + update_stat(trans); + + dns_message_reset(trans->qmessage, DNS_MESSAGE_INTENTRENDER); + dns_message_reset(trans->rmessage, DNS_MESSAGE_INTENTPARSE); + + trans->inuse = false; + if (trans->domain != NULL) + isc_mem_free(mctx, trans->domain); + trans->domain = NULL; + if (trans->qname != NULL) + dns_fixedname_invalidate(&trans->fixedname); + trans->qname = NULL; + trans->qlabel = qlabels; + trans->qname_found = false; + trans->current_ns = NULL; + + while ((pns = ISC_LIST_HEAD(trans->nslist)) != NULL) { + ISC_LIST_UNLINK(trans->nslist, pns, link); + while ((server = ISC_LIST_HEAD(pns->servers)) != NULL) { + ISC_LIST_UNLINK(pns->servers, server, link); + isc_mem_put(mctx, server, sizeof(*server)); + } + isc_mem_put(mctx, pns, sizeof(*pns)); + } + + outstanding_probes--; + + result = probe_domain(trans); + if (result == ISC_R_NOMORE && outstanding_probes == 0) + isc_app_ctxshutdown(actx); +} + +static void +resolve_ns(isc_task_t *task, isc_event_t *event) { + struct probe_trans *trans = event->ev_arg; + dns_clientresevent_t *rev = (dns_clientresevent_t *)event; + dns_name_t *name; + dns_rdataset_t *rdataset; + isc_result_t result = ISC_R_SUCCESS; + dns_rdata_t rdata = DNS_RDATA_INIT; + struct probe_ns *pns; + + REQUIRE(task == probe_task); + REQUIRE(trans->inuse == true); + INSIST(outstanding_probes > 0); + + for (name = ISC_LIST_HEAD(rev->answerlist); name != NULL; + name = ISC_LIST_NEXT(name, link)) { + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) { + (void)print_rdataset(rdataset, name); + + if (rdataset->type != dns_rdatatype_ns) + continue; + + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) { + dns_rdata_ns_t ns; + + dns_rdataset_current(rdataset, &rdata); + /* + * Extract the name from the NS record. + */ + result = dns_rdata_tostruct(&rdata, &ns, NULL); + if (result != ISC_R_SUCCESS) + continue; + + pns = isc_mem_get(mctx, sizeof(*pns)); + if (pns == NULL) { + fprintf(stderr, + "resolve_ns: mem_get failed"); + result = ISC_R_NOMEMORY; + POST(result); + /* + * XXX: should we continue with the + * available servers anyway? + */ + goto cleanup; + } + + pns->name = + dns_fixedname_initname(&pns->fixedname); + ISC_LINK_INIT(pns, link); + ISC_LIST_APPEND(trans->nslist, pns, link); + ISC_LIST_INIT(pns->servers); + + dns_name_copy(&ns.name, pns->name, NULL); + dns_rdata_reset(&rdata); + dns_rdata_freestruct(&ns); + } + } + } + + cleanup: + dns_client_freeresanswer(client, &rev->answerlist); + dns_client_destroyrestrans(&trans->resid); + isc_event_free(&event); + + if (!ISC_LIST_EMPTY(trans->nslist)) { + /* Go get addresses of NSes */ + trans->current_ns = ISC_LIST_HEAD(trans->nslist); + result = fetch_nsaddress(trans); + } else + result = ISC_R_FAILURE; + + if (result == ISC_R_SUCCESS) + return; + + reset_probe(trans); +} + +static isc_result_t +probe_domain(struct probe_trans *trans) { + isc_result_t result; + unsigned int domainlen; + isc_buffer_t b; + char buf[4096]; /* XXX ad hoc constant, but should be enough */ + char *cp; + + REQUIRE(trans != NULL); + REQUIRE(trans->inuse == false); + REQUIRE(outstanding_probes < MAX_PROBES); + + /* Construct domain */ + cp = fgets(buf, sizeof(buf), input); + if (cp == NULL) + return (ISC_R_NOMORE); + if ((cp = strchr(buf, '\n')) != NULL) /* zap NL if any */ + *cp = '\0'; + trans->domain = isc_mem_strdup(mctx, buf); + if (trans->domain == NULL) { + fprintf(stderr, + "failed to allocate memory for domain: %s", cp); + return (ISC_R_NOMEMORY); + } + + /* Start getting NS for the domain */ + domainlen = strlen(buf); + isc_buffer_init(&b, buf, domainlen); + isc_buffer_add(&b, domainlen); + trans->qname = dns_fixedname_initname(&trans->fixedname); + result = dns_name_fromtext(trans->qname, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_client_startresolve(client, trans->qname, + dns_rdataclass_in, dns_rdatatype_ns, + 0, probe_task, resolve_ns, trans, + &trans->resid); + if (result != ISC_R_SUCCESS) + goto cleanup; + + trans->inuse = true; + outstanding_probes++; + + return (ISC_R_SUCCESS); + + cleanup: + isc_mem_free(mctx, trans->domain); + dns_fixedname_invalidate(&trans->fixedname); + + return (result); +} + +ISC_PLATFORM_NORETURN_PRE static void +usage(void) ISC_PLATFORM_NORETURN_POST; + +static void +usage(void) { + fprintf(stderr, "usage: nsprobe [-d] [-v [-v...]] [-c cache_address] " + "[input_file]\n"); + + exit(1); +} + +int +main(int argc, char *argv[]) { + int i, ch, error; + struct addrinfo hints, *res; + isc_result_t result; + isc_sockaddr_t sa; + isc_sockaddrlist_t servers; + isc_taskmgr_t *taskmgr = NULL; + isc_socketmgr_t *socketmgr = NULL; + isc_timermgr_t *timermgr = NULL; + + while ((ch = isc_commandline_parse(argc, argv, "c:dhv")) != -1) { + switch (ch) { + case 'c': + cacheserver = isc_commandline_argument; + break; + case 'd': + debug_mode = true; + break; + case 'h': + usage(); + break; + case 'v': + verbose_level++; + break; + default: + usage(); + break; + } + } + + argc -= isc_commandline_index; + argv += isc_commandline_index; + + /* Common set up */ + isc_lib_register(); + result = dns_lib_init(); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "dns_lib_init failed: %u\n", result); + exit(1); + } + + result = ctxs_init(&mctx, &actx, &taskmgr, &socketmgr, + &timermgr); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "ctx create failed: %u\n", result); + exit(1); + } + + isc_app_ctxstart(actx); + + result = dns_client_createx(mctx, actx, taskmgr, socketmgr, + timermgr, 0, &client); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "dns_client_createx failed: %u\n", result); + exit(1); + } + + /* Set local cache server */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + error = getaddrinfo(cacheserver, "53", &hints, &res); + if (error != 0) { + fprintf(stderr, "failed to convert server name (%s): %s\n", + cacheserver, gai_strerror(error)); + exit(1); + } + + if (res->ai_addrlen > sizeof(sa.type)) { + fprintf(stderr, + "assumption failure: addrlen is too long: %ld\n", + (long)res->ai_addrlen); + exit(1); + } + memmove(&sa.type.sa, res->ai_addr, res->ai_addrlen); + sa.length = (unsigned int)res->ai_addrlen; + freeaddrinfo(res); + ISC_LINK_INIT(&sa, link); + ISC_LIST_INIT(servers); + ISC_LIST_APPEND(servers, &sa, link); + result = dns_client_setservers(client, dns_rdataclass_in, NULL, + &servers); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "failed to set server: %u\n", result); + exit(1); + } + + /* Create the main task */ + probe_task = NULL; + result = isc_task_create(taskmgr, 0, &probe_task); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "failed to create task: %u\n", result); + exit(1); + } + + /* Open input file */ + if (argc == 0) + input = stdin; + else { + input = fopen(argv[0], "r"); + if (input == NULL) { + fprintf(stderr, "failed to open input file: %s\n", + argv[0]); + exit(1); + } + } + + /* Set up and start probe */ + for (i = 0; i < MAX_PROBES; i++) { + probes[i].inuse = false; + probes[i].domain = NULL; + dns_fixedname_init(&probes[i].fixedname); + probes[i].qname = NULL; + probes[i].qlabel = qlabels; + probes[i].qname_found = false; + probes[i].resid = NULL; + ISC_LIST_INIT(probes[i].nslist); + probes[i].reqid = NULL; + + probes[i].qmessage = NULL; + result = dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER, + &probes[i].qmessage); + if (result == ISC_R_SUCCESS) { + result = dns_message_create(mctx, + DNS_MESSAGE_INTENTPARSE, + &probes[i].rmessage); + } + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "initialization failure\n"); + exit(1); + } + } + for (i = 0; i < MAX_PROBES; i++) { + result = probe_domain(&probes[i]); + if (result == ISC_R_NOMORE) + break; + else if (result != ISC_R_SUCCESS) { + fprintf(stderr, "failed to issue an initial probe\n"); + exit(1); + } + } + + /* Start event loop */ + isc_app_ctxrun(actx); + + /* Dump results */ + printf("Per domain results (out of %lu domains):\n", + number_of_domains); + printf(" valid: %lu\n" + " ignore: %lu\n" + " nxdomain: %lu\n" + " othererr: %lu\n" + " multiplesoa: %lu\n" + " multiplecname: %lu\n" + " brokenanswer: %lu\n" + " lame: %lu\n" + " unknown: %lu\n" + " multiple errors: %lu\n", + domain_stat.valid, domain_stat.ignore, domain_stat.nxdomain, + domain_stat.othererr, domain_stat.multiplesoa, + domain_stat.multiplecname, domain_stat.brokenanswer, + domain_stat.lame, domain_stat.unknown, multiple_error_domains); + printf("Per server results (out of %lu servers):\n", + number_of_servers); + printf(" valid: %lu\n" + " ignore: %lu\n" + " nxdomain: %lu\n" + " othererr: %lu\n" + " multiplesoa: %lu\n" + " multiplecname: %lu\n" + " brokenanswer: %lu\n" + " lame: %lu\n" + " unknown: %lu\n", + server_stat.valid, server_stat.ignore, server_stat.nxdomain, + server_stat.othererr, server_stat.multiplesoa, + server_stat.multiplecname, server_stat.brokenanswer, + server_stat.lame, server_stat.unknown); + + /* Cleanup */ + for (i = 0; i < MAX_PROBES; i++) { + dns_message_destroy(&probes[i].qmessage); + dns_message_destroy(&probes[i].rmessage); + } + isc_task_detach(&probe_task); + dns_client_destroy(&client); + dns_lib_shutdown(); + isc_app_ctxfinish(actx); + ctxs_destroy(&mctx, &actx, &taskmgr, &socketmgr, &timermgr); + + return (0); +} |