mirror of
https://github.com/isc-projects/bind9.git
synced 2026-02-26 19:41:04 -05:00
The rewrite of BIND 9 build system is a large work and cannot be reasonable split into separate merge requests. Addition of the automake has a positive effect on the readability and maintainability of the build system as it is more declarative, it allows conditional and we are able to drop all of the custom make code that BIND 9 developed over the years to overcome the deficiencies of autoconf + custom Makefile.in files. This squashed commit contains following changes: - conversion (or rather fresh rewrite) of all Makefile.in files to Makefile.am by using automake - the libtool is now properly integrated with automake (the way we used it was rather hackish as the only official way how to use libtool is via automake - the dynamic module loading was rewritten from a custom patchwork to libtool's libltdl (which includes the patchwork to support module loading on different systems internally) - conversion of the unit test executor from kyua to automake parallel driver - conversion of the system test executor from custom make/shell to automake parallel driver - The GSSAPI has been refactored, the custom SPNEGO on the basis that all major KRB5/GSSAPI (mit-krb5, heimdal and Windows) implementations support SPNEGO mechanism. - The various defunct tests from bin/tests have been removed: bin/tests/optional and bin/tests/pkcs11 - The text files generated from the MD files have been removed, the MarkDown has been designed to be readable by both humans and computers - The xsl header is now generated by a simple sed command instead of perl helper - The <irs/platform.h> header has been removed - cleanups of configure.ac script to make it more simpler, addition of multiple macros (there's still work to be done though) - the tarball can now be prepared with `make dist` - the system tests are partially able to run in oot build Here's a list of unfinished work that needs to be completed in subsequent merge requests: - `make distcheck` doesn't yet work (because of system tests oot run is not yet finished) - documentation is not yet built, there's a different merge request with docbook to sphinx-build rst conversion that needs to be rebased and adapted on top of the automake - msvc build is non functional yet and we need to decide whether we will just cross-compile bind9 using mingw-w64 or fix the msvc build - contributed dlz modules are not included neither in the autoconf nor automake
1241 lines
30 KiB
C
1241 lines
30 KiB
C
/*
|
|
* 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.
|
|
*/
|
|
|
|
#ifndef WIN32
|
|
#include <netdb.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#endif /* ifndef WIN32 */
|
|
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <isc/app.h>
|
|
#include <isc/attributes.h>
|
|
#include <isc/buffer.h>
|
|
#include <isc/commandline.h>
|
|
#include <isc/lib.h>
|
|
#include <isc/mem.h>
|
|
#include <isc/print.h>
|
|
#include <isc/sockaddr.h>
|
|
#include <isc/socket.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;
|
|
|
|
isc_mem_create(mctxp);
|
|
|
|
result = isc_appctx_create(*mctxp, actxp);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto fail;
|
|
}
|
|
|
|
result = isc_taskmgr_createinctx(*mctxp, 1, 0, taskmgrp);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto fail;
|
|
}
|
|
|
|
result = isc_socketmgr_createinctx(*mctxp, socketmgrp);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto fail;
|
|
}
|
|
|
|
result = isc_timermgr_createinctx(*mctxp, 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);
|
|
}
|
|
|
|
if (strlcpy(buf, *trans->qlabel, sizeof(buf)) >= sizeof(buf)) {
|
|
return (ISC_R_NOSPACE);
|
|
}
|
|
|
|
if ((domainlen = strlcat(buf, trans->domain, sizeof(buf))) >=
|
|
sizeof(buf)) {
|
|
return (ISC_R_NOSPACE);
|
|
}
|
|
|
|
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));
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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));
|
|
|
|
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_copynf(&ns.name, pns->name);
|
|
dns_rdata_reset(&rdata);
|
|
dns_rdata_freestruct(&ns);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
/* 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_NORETURN static void
|
|
usage(void);
|
|
|
|
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, NULL, NULL);
|
|
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);
|
|
}
|