David van Moolenbroek 00b67f09dd Import NetBSD named(8)
Also known as ISC bind.  This import adds utilities such as host(1),
dig(1), and nslookup(1), as well as many other tools and libraries.

Change-Id: I035ca46e64f1965d57019e773f4ff0ef035e4aa3
2017-03-21 22:00:06 +00:00

1226 lines
31 KiB
C

/* $NetBSD: nsprobe.c,v 1.1.1.5 2015/07/08 15:38:07 christos Exp $ */
/*
* Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC")
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/* Id */
#include <config.h>
#ifndef WIN32
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#endif
#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/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 {
isc_boolean_t inuse;
char *domain;
dns_fixedname_t fixedname;
dns_name_t *qname;
const char **qlabel;
isc_boolean_t 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 isc_boolean_t debug_mode = ISC_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, ISC_FALSE, ISC_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, ISC_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);
dns_fixedname_init(&trans->fixedname);
trans->qname = dns_fixedname_name(&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 == ISC_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: %d (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 = ISC_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 == ISC_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 = ISC_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 = ISC_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 == ISC_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;
}
dns_fixedname_init(&pns->fixedname);
pns->name =
dns_fixedname_name(&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 == ISC_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);
dns_fixedname_init(&trans->fixedname);
trans->qname = dns_fixedname_name(&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 = ISC_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 = ISC_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: %d\n", result);
exit(1);
}
result = ctxs_init(&mctx, &actx, &taskmgr, &socketmgr,
&timermgr);
if (result != ISC_R_SUCCESS) {
fprintf(stderr, "ctx create failed: %d\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: %d\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: %d\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: %d\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 = ISC_FALSE;
probes[i].domain = NULL;
dns_fixedname_init(&probes[i].fixedname);
probes[i].qname = NULL;
probes[i].qlabel = qlabels;
probes[i].qname_found = ISC_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);
}