1224090Sdougb/*
2262706Serwin * Copyright (C) 2009-2014  Internet Systems Consortium, Inc. ("ISC")
3224090Sdougb *
4224090Sdougb * Permission to use, copy, modify, and/or distribute this software for any
5224090Sdougb * purpose with or without fee is hereby granted, provided that the above
6224090Sdougb * copyright notice and this permission notice appear in all copies.
7224090Sdougb *
8224090Sdougb * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
9224090Sdougb * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10224090Sdougb * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
11224090Sdougb * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12224090Sdougb * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
13224090Sdougb * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14224090Sdougb * PERFORMANCE OF THIS SOFTWARE.
15224090Sdougb */
16224090Sdougb
17234010Sdougb/* $Id$ */
18224090Sdougb
19224090Sdougb#include <config.h>
20224090Sdougb
21224090Sdougb#include <sys/types.h>
22224090Sdougb#include <sys/socket.h>
23224090Sdougb
24224090Sdougb#include <stdio.h>
25224090Sdougb#include <stdlib.h>
26224090Sdougb#include <string.h>
27224090Sdougb#include <unistd.h>
28224090Sdougb#include <netdb.h>
29224090Sdougb
30224090Sdougb#include <isc/app.h>
31224090Sdougb#include <isc/buffer.h>
32224090Sdougb#include <isc/lib.h>
33224090Sdougb#include <isc/mem.h>
34224090Sdougb#include <isc/socket.h>
35224090Sdougb#include <isc/sockaddr.h>
36224090Sdougb#include <isc/string.h>
37224090Sdougb#include <isc/task.h>
38224090Sdougb#include <isc/timer.h>
39224090Sdougb#include <isc/util.h>
40224090Sdougb
41224090Sdougb#include <dns/client.h>
42224090Sdougb#include <dns/fixedname.h>
43224090Sdougb#include <dns/lib.h>
44224090Sdougb#include <dns/message.h>
45224090Sdougb#include <dns/name.h>
46224090Sdougb#include <dns/rdata.h>
47224090Sdougb#include <dns/rdataset.h>
48224090Sdougb#include <dns/rdatastruct.h>
49224090Sdougb#include <dns/rdatatype.h>
50224090Sdougb#include <dns/result.h>
51224090Sdougb
52224090Sdougb#define MAX_PROBES 1000
53224090Sdougb
54224090Sdougbstatic dns_client_t *client = NULL;
55224090Sdougbstatic isc_task_t *probe_task = NULL;
56224090Sdougbstatic isc_appctx_t *actx = NULL;
57224090Sdougbstatic isc_mem_t *mctx = NULL;
58224090Sdougbstatic unsigned int outstanding_probes = 0;
59224090Sdougbconst char *cacheserver = "127.0.0.1";
60224090Sdougbstatic FILE *fp;
61224090Sdougb
62224090Sdougbtypedef enum {
63224090Sdougb	none,
64224090Sdougb	exist,
65224090Sdougb	nxdomain,
66224090Sdougb	othererr,
67224090Sdougb	multiplesoa,
68224090Sdougb	multiplecname,
69224090Sdougb	brokenanswer,
70224090Sdougb	lame,
71224090Sdougb	timedout,
72224090Sdougb	notype,
73224090Sdougb	unexpected
74224090Sdougb} query_result_t;
75224090Sdougb
76224090Sdougbstruct server {
77224090Sdougb	ISC_LINK(struct server) link;
78224090Sdougb
79224090Sdougb	isc_sockaddr_t address;
80224090Sdougb	query_result_t result_a;
81224090Sdougb	query_result_t result_aaaa;
82224090Sdougb};
83224090Sdougb
84224090Sdougbstruct probe_ns {
85224090Sdougb	ISC_LINK(struct probe_ns) link;
86224090Sdougb
87224090Sdougb	dns_fixedname_t fixedname;
88224090Sdougb	dns_name_t *name;
89224090Sdougb	struct server *current_server;
90224090Sdougb	ISC_LIST(struct server) servers;
91224090Sdougb};
92224090Sdougb
93224090Sdougbstruct probe_trans {
94224090Sdougb	isc_boolean_t inuse;
95224090Sdougb	char *domain;
96224090Sdougb	dns_fixedname_t fixedname;
97224090Sdougb	dns_name_t *qname;
98224090Sdougb	const char **qlabel;
99224090Sdougb	isc_boolean_t qname_found;
100224090Sdougb	dns_clientrestrans_t *resid;
101224090Sdougb	dns_message_t *qmessage;
102224090Sdougb	dns_message_t *rmessage;
103224090Sdougb	dns_clientreqtrans_t *reqid;
104224090Sdougb
105224090Sdougb	/* NS list */
106224090Sdougb	struct probe_ns *current_ns;
107224090Sdougb	ISC_LIST(struct probe_ns) nslist;
108224090Sdougb};
109224090Sdougb
110224090Sdougbstruct lcl_stat {
111224090Sdougb	unsigned long valid;
112224090Sdougb	unsigned long ignore;
113224090Sdougb	unsigned long nxdomain;
114224090Sdougb	unsigned long othererr;
115224090Sdougb	unsigned long multiplesoa;
116224090Sdougb	unsigned long multiplecname;
117224090Sdougb	unsigned long brokenanswer;
118224090Sdougb	unsigned long lame;
119224090Sdougb	unsigned long unknown;
120224090Sdougb} server_stat, domain_stat;
121224090Sdougb
122224090Sdougbstatic unsigned long number_of_domains = 0;
123224090Sdougbstatic unsigned long number_of_servers = 0;
124224090Sdougbstatic unsigned long multiple_error_domains = 0;
125224090Sdougbstatic isc_boolean_t debug_mode = ISC_FALSE;
126224090Sdougbstatic int verbose_level = 0;
127224090Sdougbstatic const char *qlabels[] = {"www.", "ftp.", NULL};
128224090Sdougbstatic struct probe_trans probes[MAX_PROBES];
129224090Sdougb
130224090Sdougbstatic isc_result_t probe_domain(struct probe_trans *trans);
131224090Sdougbstatic void reset_probe(struct probe_trans *trans);
132224090Sdougbstatic isc_result_t fetch_nsaddress(struct probe_trans *trans);
133224090Sdougbstatic isc_result_t probe_name(struct probe_trans *trans,
134224090Sdougb			       dns_rdatatype_t type);
135224090Sdougb
136224090Sdougb/* Dump an rdataset for debug */
137224090Sdougbstatic isc_result_t
138224090Sdougbprint_rdataset(dns_rdataset_t *rdataset, dns_name_t *owner) {
139224090Sdougb	isc_buffer_t target;
140224090Sdougb	isc_result_t result;
141224090Sdougb	isc_region_t r;
142224090Sdougb	char t[4096];
143224090Sdougb
144224090Sdougb	if (!debug_mode)
145224090Sdougb		return (ISC_R_SUCCESS);
146224090Sdougb
147224090Sdougb	isc_buffer_init(&target, t, sizeof(t));
148224090Sdougb
149224090Sdougb	if (!dns_rdataset_isassociated(rdataset))
150224090Sdougb		return (ISC_R_SUCCESS);
151224090Sdougb	result = dns_rdataset_totext(rdataset, owner, ISC_FALSE, ISC_FALSE,
152224090Sdougb				     &target);
153224090Sdougb	if (result != ISC_R_SUCCESS)
154224090Sdougb		return (result);
155224090Sdougb	isc_buffer_usedregion(&target, &r);
156224090Sdougb	printf("%.*s", (int)r.length, (char *)r.base);
157224090Sdougb
158224090Sdougb	return (ISC_R_SUCCESS);
159224090Sdougb}
160224090Sdougb
161224090Sdougbstatic isc_result_t
162224090Sdougbprint_name(dns_name_t *name) {
163224090Sdougb	isc_result_t result;
164224090Sdougb	isc_buffer_t target;
165224090Sdougb	isc_region_t r;
166224090Sdougb	char t[4096];
167224090Sdougb
168224090Sdougb	isc_buffer_init(&target, t, sizeof(t));
169224090Sdougb	result = dns_name_totext(name, ISC_TRUE, &target);
170224090Sdougb	if (result == ISC_R_SUCCESS) {
171224090Sdougb		isc_buffer_usedregion(&target, &r);
172224090Sdougb		printf("%.*s", (int)r.length, (char *)r.base);
173224090Sdougb	} else
174224090Sdougb		printf("(invalid name)");
175224090Sdougb
176224090Sdougb	return (result);
177224090Sdougb}
178224090Sdougb
179224090Sdougbstatic isc_result_t
180224090Sdougbprint_address(FILE *fp, isc_sockaddr_t *addr) {
181224090Sdougb	char buf[NI_MAXHOST];
182224090Sdougb
183224090Sdougb	if (getnameinfo(&addr->type.sa, addr->length, buf, sizeof(buf),
184224090Sdougb			NULL, 0, NI_NUMERICHOST) == 0) {
185224090Sdougb		fprintf(fp, "%s", buf);
186224090Sdougb	} else {
187224090Sdougb		fprintf(fp, "(invalid address)");
188224090Sdougb	}
189224090Sdougb
190224090Sdougb	return (ISC_R_SUCCESS);
191224090Sdougb}
192224090Sdougb
193224090Sdougbstatic void
194224090Sdougbctxs_destroy(isc_mem_t **mctxp, isc_appctx_t **actxp,
195224090Sdougb	     isc_taskmgr_t **taskmgrp, isc_socketmgr_t **socketmgrp,
196224090Sdougb	     isc_timermgr_t **timermgrp)
197224090Sdougb{
198224090Sdougb	if (*taskmgrp != NULL)
199224090Sdougb		isc_taskmgr_destroy(taskmgrp);
200224090Sdougb
201224090Sdougb	if (*timermgrp != NULL)
202224090Sdougb		isc_timermgr_destroy(timermgrp);
203224090Sdougb
204224090Sdougb	if (*socketmgrp != NULL)
205224090Sdougb		isc_socketmgr_destroy(socketmgrp);
206224090Sdougb
207224090Sdougb	if (*actxp != NULL)
208224090Sdougb		isc_appctx_destroy(actxp);
209224090Sdougb
210224090Sdougb	if (*mctxp != NULL)
211224090Sdougb		isc_mem_destroy(mctxp);
212224090Sdougb}
213224090Sdougb
214224090Sdougbstatic isc_result_t
215224090Sdougbctxs_init(isc_mem_t **mctxp, isc_appctx_t **actxp,
216224090Sdougb	  isc_taskmgr_t **taskmgrp, isc_socketmgr_t **socketmgrp,
217224090Sdougb	  isc_timermgr_t **timermgrp)
218224090Sdougb{
219224090Sdougb	isc_result_t result;
220224090Sdougb
221224090Sdougb	result = isc_mem_create(0, 0, mctxp);
222224090Sdougb	if (result != ISC_R_SUCCESS)
223224090Sdougb		goto fail;
224224090Sdougb
225224090Sdougb	result = isc_appctx_create(*mctxp, actxp);
226224090Sdougb	if (result != ISC_R_SUCCESS)
227224090Sdougb		goto fail;
228224090Sdougb
229224090Sdougb	result = isc_taskmgr_createinctx(*mctxp, *actxp, 1, 0, taskmgrp);
230224090Sdougb	if (result != ISC_R_SUCCESS)
231224090Sdougb		goto fail;
232224090Sdougb
233224090Sdougb	result = isc_socketmgr_createinctx(*mctxp, *actxp, socketmgrp);
234224090Sdougb	if (result != ISC_R_SUCCESS)
235224090Sdougb		goto fail;
236224090Sdougb
237224090Sdougb	result = isc_timermgr_createinctx(*mctxp, *actxp, timermgrp);
238224090Sdougb	if (result != ISC_R_SUCCESS)
239224090Sdougb		goto fail;
240224090Sdougb
241224090Sdougb	return (ISC_R_SUCCESS);
242224090Sdougb
243224090Sdougb fail:
244224090Sdougb	ctxs_destroy(mctxp, actxp, taskmgrp, socketmgrp, timermgrp);
245224090Sdougb
246224090Sdougb	return (result);
247224090Sdougb}
248224090Sdougb
249224090Sdougb/*
250224090Sdougb * Common routine to make query data
251224090Sdougb */
252224090Sdougbstatic isc_result_t
253224090Sdougbmake_querymessage(dns_message_t *message, dns_name_t *qname0,
254224090Sdougb		  dns_rdatatype_t rdtype)
255224090Sdougb{
256224090Sdougb	dns_name_t *qname = NULL;
257224090Sdougb	dns_rdataset_t *qrdataset = NULL;
258224090Sdougb	isc_result_t result;
259224090Sdougb
260224090Sdougb	message->opcode = dns_opcode_query;
261224090Sdougb	message->rdclass = dns_rdataclass_in;
262224090Sdougb
263224090Sdougb	result = dns_message_gettempname(message, &qname);
264224090Sdougb	if (result != ISC_R_SUCCESS)
265224090Sdougb		goto cleanup;
266224090Sdougb
267224090Sdougb	result = dns_message_gettemprdataset(message, &qrdataset);
268224090Sdougb	if (result != ISC_R_SUCCESS)
269224090Sdougb		goto cleanup;
270224090Sdougb
271224090Sdougb	dns_name_init(qname, NULL);
272224090Sdougb	dns_name_clone(qname0, qname);
273224090Sdougb	dns_rdataset_init(qrdataset);
274224090Sdougb	dns_rdataset_makequestion(qrdataset, message->rdclass, rdtype);
275224090Sdougb	ISC_LIST_APPEND(qname->list, qrdataset, link);
276224090Sdougb	dns_message_addname(message, qname, DNS_SECTION_QUESTION);
277224090Sdougb
278224090Sdougb	return (ISC_R_SUCCESS);
279224090Sdougb
280224090Sdougb cleanup:
281224090Sdougb	if (qname != NULL)
282224090Sdougb		dns_message_puttempname(message, &qname);
283224090Sdougb	if (qrdataset != NULL)
284224090Sdougb		dns_message_puttemprdataset(message, &qrdataset);
285224090Sdougb	return (result);
286224090Sdougb}
287224090Sdougb
288224090Sdougb/*
289224090Sdougb * Update statistics
290224090Sdougb */
291224090Sdougbstatic inline void
292224090Sdougbincrement_entry(unsigned long *entryp) {
293224090Sdougb	(*entryp)++;
294254402Serwin	INSIST(*entryp != 0U);	/* check overflow */
295224090Sdougb}
296224090Sdougb
297224090Sdougbstatic void
298224090Sdougbupdate_stat(struct probe_trans *trans) {
299224090Sdougb	struct probe_ns *pns;
300224090Sdougb	struct server *server;
301224090Sdougb	struct lcl_stat local_stat;
302224090Sdougb	unsigned int err_count = 0;
303224090Sdougb	const char *stattype;
304224090Sdougb
305224090Sdougb	increment_entry(&number_of_domains);
306224090Sdougb	memset(&local_stat, 0, sizeof(local_stat));
307224090Sdougb
308224090Sdougb	/* Update per sever statistics */
309224090Sdougb	for (pns = ISC_LIST_HEAD(trans->nslist); pns != NULL;
310224090Sdougb	     pns = ISC_LIST_NEXT(pns, link)) {
311224090Sdougb		for (server = ISC_LIST_HEAD(pns->servers); server != NULL;
312224090Sdougb		     server = ISC_LIST_NEXT(server, link)) {
313224090Sdougb			increment_entry(&number_of_servers);
314224090Sdougb
315224090Sdougb			if (server->result_aaaa == exist ||
316224090Sdougb			    server->result_aaaa == notype) {
317224090Sdougb				/*
318224090Sdougb				 * Don't care about the result of A query if
319224090Sdougb				 * the answer to AAAA query was expected.
320224090Sdougb				 */
321224090Sdougb				stattype = "valid";
322224090Sdougb				increment_entry(&server_stat.valid);
323224090Sdougb				increment_entry(&local_stat.valid);
324224090Sdougb			} else if (server->result_a == exist) {
325224090Sdougb				switch (server->result_aaaa) {
326224090Sdougb				case exist:
327224090Sdougb				case notype:
328224090Sdougb					stattype = "valid";
329224090Sdougb					increment_entry(&server_stat.valid);
330224090Sdougb					increment_entry(&local_stat.valid);
331224090Sdougb					break;
332224090Sdougb				case timedout:
333224090Sdougb					stattype = "ignore";
334224090Sdougb					increment_entry(&server_stat.ignore);
335224090Sdougb					increment_entry(&local_stat.ignore);
336224090Sdougb					break;
337224090Sdougb				case nxdomain:
338224090Sdougb					stattype = "nxdomain";
339224090Sdougb					increment_entry(&server_stat.nxdomain);
340224090Sdougb					increment_entry(&local_stat.nxdomain);
341224090Sdougb					break;
342224090Sdougb				case othererr:
343224090Sdougb					stattype = "othererr";
344224090Sdougb					increment_entry(&server_stat.othererr);
345224090Sdougb					increment_entry(&local_stat.othererr);
346224090Sdougb					break;
347224090Sdougb				case multiplesoa:
348224090Sdougb					stattype = "multiplesoa";
349224090Sdougb					increment_entry(&server_stat.multiplesoa);
350224090Sdougb					increment_entry(&local_stat.multiplesoa);
351224090Sdougb					break;
352224090Sdougb				case multiplecname:
353224090Sdougb					stattype = "multiplecname";
354224090Sdougb					increment_entry(&server_stat.multiplecname);
355224090Sdougb					increment_entry(&local_stat.multiplecname);
356224090Sdougb					break;
357224090Sdougb				case brokenanswer:
358224090Sdougb					stattype = "brokenanswer";
359224090Sdougb					increment_entry(&server_stat.brokenanswer);
360224090Sdougb					increment_entry(&local_stat.brokenanswer);
361224090Sdougb					break;
362224090Sdougb				case lame:
363224090Sdougb					stattype = "lame";
364224090Sdougb					increment_entry(&server_stat.lame);
365224090Sdougb					increment_entry(&local_stat.lame);
366224090Sdougb					break;
367224090Sdougb				default:
368224090Sdougb					stattype = "unknown";
369224090Sdougb					increment_entry(&server_stat.unknown);
370224090Sdougb					increment_entry(&local_stat.unknown);
371224090Sdougb					break;
372224090Sdougb				}
373224090Sdougb			} else {
374224090Sdougb				stattype = "unknown";
375224090Sdougb				increment_entry(&server_stat.unknown);
376224090Sdougb				increment_entry(&local_stat.unknown);
377224090Sdougb			}
378224090Sdougb
379224090Sdougb			if (verbose_level > 1 ||
380224090Sdougb			    (verbose_level == 1 &&
381224090Sdougb			     strcmp(stattype, "valid") != 0 &&
382224090Sdougb			     strcmp(stattype, "unknown") != 0)) {
383224090Sdougb				print_name(pns->name);
384224090Sdougb				putchar('(');
385224090Sdougb				print_address(stdout, &server->address);
386224090Sdougb				printf(") for %s:%s\n", trans->domain,
387224090Sdougb				       stattype);
388224090Sdougb			}
389224090Sdougb		}
390224090Sdougb	}
391224090Sdougb
392224090Sdougb	/* Update per domain statistics */
393254402Serwin	if (local_stat.ignore > 0U) {
394224090Sdougb		if (verbose_level > 0)
395224090Sdougb			printf("%s:ignore\n", trans->domain);
396224090Sdougb		increment_entry(&domain_stat.ignore);
397224090Sdougb		err_count++;
398224090Sdougb	}
399254402Serwin	if (local_stat.nxdomain > 0U) {
400224090Sdougb		if (verbose_level > 0)
401224090Sdougb			printf("%s:nxdomain\n", trans->domain);
402224090Sdougb		increment_entry(&domain_stat.nxdomain);
403224090Sdougb		err_count++;
404224090Sdougb	}
405254402Serwin	if (local_stat.othererr > 0U) {
406224090Sdougb		if (verbose_level > 0)
407224090Sdougb			printf("%s:othererr\n", trans->domain);
408224090Sdougb		increment_entry(&domain_stat.othererr);
409224090Sdougb		err_count++;
410224090Sdougb	}
411254402Serwin	if (local_stat.multiplesoa > 0U) {
412224090Sdougb		if (verbose_level > 0)
413224090Sdougb			printf("%s:multiplesoa\n", trans->domain);
414224090Sdougb		increment_entry(&domain_stat.multiplesoa);
415224090Sdougb		err_count++;
416224090Sdougb	}
417254402Serwin	if (local_stat.multiplecname > 0U) {
418224090Sdougb		if (verbose_level > 0)
419224090Sdougb			printf("%s:multiplecname\n", trans->domain);
420224090Sdougb		increment_entry(&domain_stat.multiplecname);
421224090Sdougb		err_count++;
422224090Sdougb	}
423254402Serwin	if (local_stat.brokenanswer > 0U) {
424224090Sdougb		if (verbose_level > 0)
425224090Sdougb			printf("%s:brokenanswer\n", trans->domain);
426224090Sdougb		increment_entry(&domain_stat.brokenanswer);
427224090Sdougb		err_count++;
428224090Sdougb	}
429254402Serwin	if (local_stat.lame > 0U) {
430224090Sdougb		if (verbose_level > 0)
431224090Sdougb			printf("%s:lame\n", trans->domain);
432224090Sdougb		increment_entry(&domain_stat.lame);
433224090Sdougb		err_count++;
434224090Sdougb	}
435224090Sdougb
436254402Serwin	if (err_count > 1U)
437224090Sdougb		increment_entry(&multiple_error_domains);
438224090Sdougb
439224090Sdougb	/*
440224090Sdougb	 * We regard the domain as valid if and only if no authoritative server
441224090Sdougb	 * has a problem and at least one server is known to be valid.
442224090Sdougb	 */
443254402Serwin	if (local_stat.valid > 0U && err_count == 0U) {
444224090Sdougb		if (verbose_level > 1)
445224090Sdougb			printf("%s:valid\n", trans->domain);
446224090Sdougb		increment_entry(&domain_stat.valid);
447224090Sdougb	}
448224090Sdougb
449224090Sdougb	/*
450224090Sdougb	 * If the domain has no available server or all servers have the
451224090Sdougb	 * 'unknown' result, the domain's result is also regarded as unknown.
452224090Sdougb	 */
453254402Serwin	if (local_stat.valid == 0U && err_count == 0U) {
454224090Sdougb		if (verbose_level > 1)
455224090Sdougb			printf("%s:unknown\n", trans->domain);
456224090Sdougb		increment_entry(&domain_stat.unknown);
457224090Sdougb	}
458224090Sdougb}
459224090Sdougb
460224090Sdougb/*
461224090Sdougb * Search for an existent name with an A RR
462224090Sdougb */
463224090Sdougb
464224090Sdougbstatic isc_result_t
465224090Sdougbset_nextqname(struct probe_trans *trans) {
466224090Sdougb	isc_result_t result;
467224090Sdougb	size_t domainlen;
468224090Sdougb	isc_buffer_t b;
469224090Sdougb	char buf[4096];	/* XXX ad-hoc constant, but should be enough */
470224090Sdougb
471224090Sdougb	if (*trans->qlabel == NULL)
472224090Sdougb		return (ISC_R_NOMORE);
473224090Sdougb
474224090Sdougb	result = isc_string_copy(buf, sizeof(buf), *trans->qlabel);
475224090Sdougb	if (result != ISC_R_SUCCESS)
476224090Sdougb		return (result);
477224090Sdougb	result = isc_string_append(buf, sizeof(buf), trans->domain);
478224090Sdougb	if (result != ISC_R_SUCCESS)
479224090Sdougb		return (result);
480224090Sdougb
481224090Sdougb	domainlen = strlen(buf);
482224090Sdougb	isc_buffer_init(&b, buf, domainlen);
483224090Sdougb	isc_buffer_add(&b, domainlen);
484224090Sdougb	dns_fixedname_init(&trans->fixedname);
485224090Sdougb	trans->qname = dns_fixedname_name(&trans->fixedname);
486224090Sdougb	result = dns_name_fromtext(trans->qname, &b, dns_rootname,
487224090Sdougb				   0, NULL);
488224090Sdougb
489224090Sdougb	trans->qlabel++;
490224090Sdougb
491224090Sdougb	return (result);
492224090Sdougb}
493224090Sdougb
494224090Sdougbstatic void
495224090Sdougbrequest_done(isc_task_t *task, isc_event_t *event) {
496224090Sdougb	struct probe_trans *trans = event->ev_arg;
497224090Sdougb	dns_clientreqevent_t *rev = (dns_clientreqevent_t *)event;
498224090Sdougb	dns_message_t *rmessage;
499224090Sdougb	struct probe_ns *pns;
500224090Sdougb	struct server *server;
501224090Sdougb	isc_result_t result;
502224090Sdougb	query_result_t *resultp;
503224090Sdougb	dns_name_t *name;
504224090Sdougb	dns_rdataset_t *rdataset;
505224090Sdougb	dns_rdatatype_t type;
506224090Sdougb
507224090Sdougb	REQUIRE(task == probe_task);
508224090Sdougb	REQUIRE(trans != NULL && trans->inuse == ISC_TRUE);
509224090Sdougb	rmessage = rev->rmessage;
510224090Sdougb	REQUIRE(rmessage == trans->rmessage);
511224090Sdougb	INSIST(outstanding_probes > 0);
512224090Sdougb
513224090Sdougb	server = trans->current_ns->current_server;
514224090Sdougb	INSIST(server != NULL);
515224090Sdougb
516224090Sdougb	if (server->result_a == none) {
517224090Sdougb		type = dns_rdatatype_a;
518224090Sdougb		resultp = &server->result_a;
519224090Sdougb	} else {
520224090Sdougb		resultp = &server->result_aaaa;
521224090Sdougb		type = dns_rdatatype_aaaa;
522224090Sdougb	}
523224090Sdougb
524224090Sdougb	if (rev->result == ISC_R_SUCCESS) {
525224090Sdougb		if ((rmessage->flags & DNS_MESSAGEFLAG_AA) == 0)
526224090Sdougb			*resultp = lame;
527224090Sdougb		else if (rmessage->rcode == dns_rcode_nxdomain)
528224090Sdougb			*resultp = nxdomain;
529224090Sdougb		else if (rmessage->rcode != dns_rcode_noerror)
530224090Sdougb			*resultp = othererr;
531224090Sdougb		else if (rmessage->counts[DNS_SECTION_ANSWER] == 0) {
532224090Sdougb			/* no error but empty answer */
533224090Sdougb			*resultp = notype;
534224090Sdougb		} else {
535224090Sdougb			result = dns_message_firstname(rmessage,
536224090Sdougb						       DNS_SECTION_ANSWER);
537224090Sdougb			while (result == ISC_R_SUCCESS) {
538224090Sdougb				name = NULL;
539224090Sdougb				dns_message_currentname(rmessage,
540224090Sdougb							DNS_SECTION_ANSWER,
541224090Sdougb							&name);
542224090Sdougb				for (rdataset = ISC_LIST_HEAD(name->list);
543224090Sdougb				     rdataset != NULL;
544224090Sdougb				     rdataset = ISC_LIST_NEXT(rdataset,
545224090Sdougb							      link)) {
546224090Sdougb					(void)print_rdataset(rdataset, name);
547224090Sdougb
548224090Sdougb					if (rdataset->type ==
549224090Sdougb					    dns_rdatatype_cname ||
550224090Sdougb					    rdataset->type ==
551224090Sdougb					    dns_rdatatype_dname) {
552224090Sdougb						/* Should chase the chain? */
553224090Sdougb						*resultp = exist;
554224090Sdougb						goto found;
555224090Sdougb					} else if (rdataset->type == type) {
556224090Sdougb						*resultp = exist;
557224090Sdougb						goto found;
558224090Sdougb					}
559224090Sdougb				}
560224090Sdougb				result = dns_message_nextname(rmessage,
561224090Sdougb							      DNS_SECTION_ANSWER);
562224090Sdougb			}
563224090Sdougb
564224090Sdougb			/*
565224090Sdougb			 * Something unexpected happened: the response
566224090Sdougb			 * contained a non-empty authoritative answer, but we
567224090Sdougb			 * could not find an expected result.
568224090Sdougb			 */
569224090Sdougb			*resultp = unexpected;
570224090Sdougb		}
571224090Sdougb	} else if (rev->result == DNS_R_RECOVERABLE ||
572224090Sdougb		   rev->result == DNS_R_BADLABELTYPE) {
573224090Sdougb		/* Broken response.  Try identifying known cases. */
574224090Sdougb		*resultp = brokenanswer;
575224090Sdougb
576224090Sdougb		if (rmessage->counts[DNS_SECTION_ANSWER] > 0) {
577224090Sdougb			result = dns_message_firstname(rmessage,
578224090Sdougb						       DNS_SECTION_ANSWER);
579224090Sdougb			while (result == ISC_R_SUCCESS) {
580224090Sdougb				/*
581224090Sdougb				 * Check to see if the response has multiple
582224090Sdougb				 * CNAME RRs.  Update the result code if so.
583224090Sdougb				 */
584224090Sdougb				name = NULL;
585224090Sdougb				dns_message_currentname(rmessage,
586224090Sdougb							DNS_SECTION_ANSWER,
587224090Sdougb							&name);
588224090Sdougb				for (rdataset = ISC_LIST_HEAD(name->list);
589224090Sdougb				     rdataset != NULL;
590224090Sdougb				     rdataset = ISC_LIST_NEXT(rdataset,
591224090Sdougb							      link)) {
592224090Sdougb					if (rdataset->type ==
593224090Sdougb					    dns_rdatatype_cname &&
594224090Sdougb					    dns_rdataset_count(rdataset) > 1) {
595224090Sdougb						*resultp = multiplecname;
596224090Sdougb						goto found;
597224090Sdougb					}
598224090Sdougb				}
599224090Sdougb				result = dns_message_nextname(rmessage,
600224090Sdougb							      DNS_SECTION_ANSWER);
601224090Sdougb			}
602224090Sdougb		}
603224090Sdougb
604224090Sdougb		if (rmessage->counts[DNS_SECTION_AUTHORITY] > 0) {
605224090Sdougb			result = dns_message_firstname(rmessage,
606224090Sdougb						       DNS_SECTION_AUTHORITY);
607224090Sdougb			while (result == ISC_R_SUCCESS) {
608224090Sdougb				/*
609224090Sdougb				 * Check to see if the response has multiple
610224090Sdougb				 * SOA RRs.  Update the result code if so.
611224090Sdougb				 */
612224090Sdougb				name = NULL;
613224090Sdougb				dns_message_currentname(rmessage,
614224090Sdougb							DNS_SECTION_AUTHORITY,
615224090Sdougb							&name);
616224090Sdougb				for (rdataset = ISC_LIST_HEAD(name->list);
617224090Sdougb				     rdataset != NULL;
618224090Sdougb				     rdataset = ISC_LIST_NEXT(rdataset,
619224090Sdougb							      link)) {
620224090Sdougb					if (rdataset->type ==
621224090Sdougb					    dns_rdatatype_soa &&
622224090Sdougb					    dns_rdataset_count(rdataset) > 1) {
623224090Sdougb						*resultp = multiplesoa;
624224090Sdougb						goto found;
625224090Sdougb					}
626224090Sdougb				}
627224090Sdougb				result = dns_message_nextname(rmessage,
628224090Sdougb							      DNS_SECTION_AUTHORITY);
629224090Sdougb			}
630224090Sdougb		}
631224090Sdougb	} else if (rev->result == ISC_R_TIMEDOUT)
632224090Sdougb		*resultp = timedout;
633224090Sdougb	else {
634224090Sdougb		fprintf(stderr, "unexpected result: %d (domain=%s, server=",
635224090Sdougb			rev->result, trans->domain);
636224090Sdougb		print_address(stderr, &server->address);
637224090Sdougb		fputc('\n', stderr);
638224090Sdougb		*resultp = unexpected;
639224090Sdougb	}
640224090Sdougb
641224090Sdougb found:
642224090Sdougb	INSIST(*resultp != none);
643224090Sdougb	if (type == dns_rdatatype_a && *resultp == exist)
644224090Sdougb		trans->qname_found = ISC_TRUE;
645224090Sdougb
646224090Sdougb	dns_client_destroyreqtrans(&trans->reqid);
647224090Sdougb	isc_event_free(&event);
648224090Sdougb	dns_message_reset(trans->rmessage, DNS_MESSAGE_INTENTPARSE);
649224090Sdougb
650224090Sdougb	result = probe_name(trans, type);
651224090Sdougb	if (result == ISC_R_NOMORE) {
652224090Sdougb		/* We've tried all addresses of all servers. */
653224090Sdougb		if (type == dns_rdatatype_a && trans->qname_found) {
654224090Sdougb			/*
655224090Sdougb			 * If we've explored A RRs and found an existent
656224090Sdougb			 * record, we can move to AAAA.
657224090Sdougb			 */
658224090Sdougb			trans->current_ns = ISC_LIST_HEAD(trans->nslist);
659224090Sdougb			probe_name(trans, dns_rdatatype_aaaa);
660224090Sdougb			result = ISC_R_SUCCESS;
661224090Sdougb		} else if (type == dns_rdatatype_a) {
662224090Sdougb			/*
663224090Sdougb			 * No server provided an existent A RR of this name.
664224090Sdougb			 * Try next label.
665224090Sdougb			 */
666224090Sdougb			dns_fixedname_invalidate(&trans->fixedname);
667224090Sdougb			trans->qname = NULL;
668224090Sdougb			result = set_nextqname(trans);
669224090Sdougb			if (result == ISC_R_SUCCESS) {
670224090Sdougb				trans->current_ns =
671224090Sdougb					ISC_LIST_HEAD(trans->nslist);
672224090Sdougb				for (pns = trans->current_ns; pns != NULL;
673224090Sdougb				     pns = ISC_LIST_NEXT(pns, link)) {
674224090Sdougb					for (server = ISC_LIST_HEAD(pns->servers);
675224090Sdougb					     server != NULL;
676224090Sdougb					     server = ISC_LIST_NEXT(server,
677224090Sdougb								    link)) {
678224090Sdougb						INSIST(server->result_aaaa ==
679224090Sdougb						       none);
680224090Sdougb						server->result_a = none;
681224090Sdougb					}
682224090Sdougb				}
683224090Sdougb				result = probe_name(trans, dns_rdatatype_a);
684224090Sdougb			}
685224090Sdougb		}
686224090Sdougb		if (result != ISC_R_SUCCESS) {
687224090Sdougb			/*
688224090Sdougb			 * We've explored AAAA RRs or failed to find a valid
689224090Sdougb			 * query label.  Wrap up the result and move to the
690224090Sdougb			 * next domain.
691224090Sdougb			 */
692224090Sdougb			reset_probe(trans);
693224090Sdougb		}
694224090Sdougb	} else if (result != ISC_R_SUCCESS)
695224090Sdougb		reset_probe(trans); /* XXX */
696224090Sdougb}
697224090Sdougb
698224090Sdougbstatic isc_result_t
699224090Sdougbprobe_name(struct probe_trans *trans, dns_rdatatype_t type) {
700224090Sdougb	isc_result_t result;
701224090Sdougb	struct probe_ns *pns;
702224090Sdougb	struct server *server;
703224090Sdougb
704224090Sdougb	REQUIRE(trans->reqid == NULL);
705224090Sdougb	REQUIRE(type == dns_rdatatype_a || type == dns_rdatatype_aaaa);
706224090Sdougb
707224090Sdougb	for (pns = trans->current_ns; pns != NULL;
708224090Sdougb	     pns = ISC_LIST_NEXT(pns, link)) {
709224090Sdougb		for (server = ISC_LIST_HEAD(pns->servers); server != NULL;
710224090Sdougb		     server = ISC_LIST_NEXT(server, link)) {
711224090Sdougb			if ((type == dns_rdatatype_a &&
712224090Sdougb			     server->result_a == none) ||
713224090Sdougb			    (type == dns_rdatatype_aaaa &&
714224090Sdougb			     server->result_aaaa == none)) {
715224090Sdougb				pns->current_server = server;
716224090Sdougb				goto found;
717224090Sdougb			}
718224090Sdougb		}
719224090Sdougb	}
720224090Sdougb
721224090Sdougb found:
722224090Sdougb	trans->current_ns = pns;
723224090Sdougb	if (pns == NULL)
724224090Sdougb		return (ISC_R_NOMORE);
725224090Sdougb
726224090Sdougb	INSIST(pns->current_server != NULL);
727224090Sdougb	dns_message_reset(trans->qmessage, DNS_MESSAGE_INTENTRENDER);
728224090Sdougb	result = make_querymessage(trans->qmessage, trans->qname, type);
729224090Sdougb	if (result != ISC_R_SUCCESS)
730224090Sdougb		return (result);
731224090Sdougb	result = dns_client_startrequest(client, trans->qmessage,
732224090Sdougb					 trans->rmessage,
733224090Sdougb					 &pns->current_server->address,
734224090Sdougb					 0, DNS_MESSAGEPARSE_BESTEFFORT,
735224090Sdougb					 NULL, 120, 0, 4,
736224090Sdougb					 probe_task, request_done, trans,
737224090Sdougb					 &trans->reqid);
738224090Sdougb
739224090Sdougb	return (result);
740224090Sdougb}
741224090Sdougb
742224090Sdougb/*
743224090Sdougb * Get IP addresses of NSes
744224090Sdougb */
745224090Sdougb
746224090Sdougbstatic void
747224090Sdougbresolve_nsaddress(isc_task_t *task, isc_event_t *event) {
748224090Sdougb	struct probe_trans *trans = event->ev_arg;
749224090Sdougb	dns_clientresevent_t *rev = (dns_clientresevent_t *)event;
750224090Sdougb	dns_name_t *name;
751224090Sdougb	dns_rdataset_t *rdataset;
752224090Sdougb	dns_rdata_t rdata = DNS_RDATA_INIT;
753224090Sdougb	struct probe_ns *pns = trans->current_ns;
754224090Sdougb	isc_result_t result;
755224090Sdougb
756224090Sdougb	REQUIRE(task == probe_task);
757224090Sdougb	REQUIRE(trans->inuse == ISC_TRUE);
758224090Sdougb	REQUIRE(pns != NULL);
759224090Sdougb	INSIST(outstanding_probes > 0);
760224090Sdougb
761224090Sdougb	for (name = ISC_LIST_HEAD(rev->answerlist); name != NULL;
762224090Sdougb	     name = ISC_LIST_NEXT(name, link)) {
763224090Sdougb		for (rdataset = ISC_LIST_HEAD(name->list);
764224090Sdougb		     rdataset != NULL;
765224090Sdougb		     rdataset = ISC_LIST_NEXT(rdataset, link)) {
766224090Sdougb			(void)print_rdataset(rdataset, name);
767224090Sdougb
768224090Sdougb			if (rdataset->type != dns_rdatatype_a)
769224090Sdougb				continue;
770224090Sdougb
771224090Sdougb			for (result = dns_rdataset_first(rdataset);
772224090Sdougb			     result == ISC_R_SUCCESS;
773224090Sdougb			     result = dns_rdataset_next(rdataset)) {
774224090Sdougb				dns_rdata_in_a_t rdata_a;
775224090Sdougb				struct server *server;
776224090Sdougb
777224090Sdougb				dns_rdataset_current(rdataset, &rdata);
778224090Sdougb				result = dns_rdata_tostruct(&rdata, &rdata_a,
779224090Sdougb							    NULL);
780224090Sdougb				if (result != ISC_R_SUCCESS)
781224090Sdougb					continue;
782224090Sdougb
783224090Sdougb				server = isc_mem_get(mctx, sizeof(*server));
784224090Sdougb				if (server == NULL) {
785224090Sdougb					fprintf(stderr, "resolve_nsaddress: "
786224090Sdougb						"mem_get failed");
787224090Sdougb					result = ISC_R_NOMEMORY;
788225361Sdougb					POST(result);
789224090Sdougb					goto cleanup;
790224090Sdougb				}
791224090Sdougb				isc_sockaddr_fromin(&server->address,
792224090Sdougb						    &rdata_a.in_addr, 53);
793224090Sdougb				ISC_LINK_INIT(server, link);
794224090Sdougb				server->result_a = none;
795224090Sdougb				server->result_aaaa = none;
796224090Sdougb				ISC_LIST_APPEND(pns->servers, server, link);
797224090Sdougb			}
798224090Sdougb		}
799224090Sdougb	}
800224090Sdougb
801224090Sdougb cleanup:
802224090Sdougb	dns_client_freeresanswer(client, &rev->answerlist);
803224090Sdougb	dns_client_destroyrestrans(&trans->resid);
804224090Sdougb	isc_event_free(&event);
805224090Sdougb
806224090Sdougb next_ns:
807224090Sdougb	trans->current_ns = ISC_LIST_NEXT(pns, link);
808224090Sdougb	if (trans->current_ns == NULL) {
809224090Sdougb		trans->current_ns = ISC_LIST_HEAD(trans->nslist);
810224090Sdougb		dns_fixedname_invalidate(&trans->fixedname);
811224090Sdougb		trans->qname = NULL;
812224090Sdougb		result = set_nextqname(trans);
813224090Sdougb		if (result == ISC_R_SUCCESS)
814224090Sdougb			 result = probe_name(trans, dns_rdatatype_a);
815224090Sdougb	} else {
816224090Sdougb		result = fetch_nsaddress(trans);
817224090Sdougb		if (result != ISC_R_SUCCESS)
818224090Sdougb			goto next_ns; /* XXX: this is unlikely to succeed */
819224090Sdougb	}
820224090Sdougb
821224090Sdougb	if (result != ISC_R_SUCCESS)
822224090Sdougb		reset_probe(trans);
823224090Sdougb}
824224090Sdougb
825224090Sdougbstatic isc_result_t
826224090Sdougbfetch_nsaddress(struct probe_trans *trans) {
827224090Sdougb	struct probe_ns *pns;
828224090Sdougb
829224090Sdougb	pns = trans->current_ns;
830224090Sdougb	REQUIRE(pns != NULL);
831224090Sdougb
832224090Sdougb	return (dns_client_startresolve(client, pns->name, dns_rdataclass_in,
833224090Sdougb					dns_rdatatype_a, 0, probe_task,
834224090Sdougb					resolve_nsaddress, trans,
835224090Sdougb					&trans->resid));
836224090Sdougb}
837224090Sdougb
838224090Sdougb/*
839224090Sdougb * Get NS RRset for a given domain
840224090Sdougb */
841224090Sdougb
842224090Sdougbstatic void
843224090Sdougbreset_probe(struct probe_trans *trans) {
844224090Sdougb	struct probe_ns *pns;
845224090Sdougb	struct server *server;
846224090Sdougb	isc_result_t result;
847224090Sdougb
848224090Sdougb	REQUIRE(trans->resid == NULL);
849224090Sdougb	REQUIRE(trans->reqid == NULL);
850224090Sdougb
851224090Sdougb	update_stat(trans);
852224090Sdougb
853224090Sdougb	dns_message_reset(trans->qmessage, DNS_MESSAGE_INTENTRENDER);
854224090Sdougb	dns_message_reset(trans->rmessage, DNS_MESSAGE_INTENTPARSE);
855224090Sdougb
856224090Sdougb	trans->inuse = ISC_FALSE;
857224090Sdougb	if (trans->domain != NULL)
858224090Sdougb		isc_mem_free(mctx, trans->domain);
859224090Sdougb	trans->domain = NULL;
860224090Sdougb	if (trans->qname != NULL)
861224090Sdougb		dns_fixedname_invalidate(&trans->fixedname);
862224090Sdougb	trans->qname = NULL;
863224090Sdougb	trans->qlabel = qlabels;
864224090Sdougb	trans->qname_found = ISC_FALSE;
865224090Sdougb	trans->current_ns = NULL;
866224090Sdougb
867224090Sdougb	while ((pns = ISC_LIST_HEAD(trans->nslist)) != NULL) {
868224090Sdougb		ISC_LIST_UNLINK(trans->nslist, pns, link);
869224090Sdougb		while ((server = ISC_LIST_HEAD(pns->servers)) != NULL) {
870224090Sdougb			ISC_LIST_UNLINK(pns->servers, server, link);
871224090Sdougb			isc_mem_put(mctx, server, sizeof(*server));
872224090Sdougb		}
873224090Sdougb		isc_mem_put(mctx, pns, sizeof(*pns));
874224090Sdougb	}
875224090Sdougb
876224090Sdougb	outstanding_probes--;
877224090Sdougb
878224090Sdougb	result = probe_domain(trans);
879224090Sdougb	if (result == ISC_R_NOMORE && outstanding_probes == 0)
880224090Sdougb		isc_app_ctxshutdown(actx);
881224090Sdougb}
882224090Sdougb
883224090Sdougbstatic void
884224090Sdougbresolve_ns(isc_task_t *task, isc_event_t *event) {
885224090Sdougb	struct probe_trans *trans = event->ev_arg;
886224090Sdougb	dns_clientresevent_t *rev = (dns_clientresevent_t *)event;
887224090Sdougb	dns_name_t *name;
888224090Sdougb	dns_rdataset_t *rdataset;
889224090Sdougb	isc_result_t result = ISC_R_SUCCESS;
890224090Sdougb	dns_rdata_t rdata = DNS_RDATA_INIT;
891224090Sdougb	struct probe_ns *pns;
892224090Sdougb
893224090Sdougb	REQUIRE(task == probe_task);
894224090Sdougb	REQUIRE(trans->inuse == ISC_TRUE);
895224090Sdougb	INSIST(outstanding_probes > 0);
896224090Sdougb
897224090Sdougb	for (name = ISC_LIST_HEAD(rev->answerlist); name != NULL;
898224090Sdougb	     name = ISC_LIST_NEXT(name, link)) {
899224090Sdougb		for (rdataset = ISC_LIST_HEAD(name->list);
900224090Sdougb		     rdataset != NULL;
901224090Sdougb		     rdataset = ISC_LIST_NEXT(rdataset, link)) {
902224090Sdougb			(void)print_rdataset(rdataset, name);
903224090Sdougb
904224090Sdougb			if (rdataset->type != dns_rdatatype_ns)
905224090Sdougb				continue;
906224090Sdougb
907224090Sdougb			for (result = dns_rdataset_first(rdataset);
908224090Sdougb			     result == ISC_R_SUCCESS;
909224090Sdougb			     result = dns_rdataset_next(rdataset)) {
910224090Sdougb				dns_rdata_ns_t ns;
911224090Sdougb
912224090Sdougb				dns_rdataset_current(rdataset, &rdata);
913224090Sdougb				/*
914224090Sdougb				 * Extract the name from the NS record.
915224090Sdougb				 */
916224090Sdougb				result = dns_rdata_tostruct(&rdata, &ns, NULL);
917224090Sdougb				if (result != ISC_R_SUCCESS)
918224090Sdougb					continue;
919224090Sdougb
920224090Sdougb				pns = isc_mem_get(mctx, sizeof(*pns));
921224090Sdougb				if (pns == NULL) {
922224090Sdougb					fprintf(stderr,
923224090Sdougb						"resolve_ns: mem_get failed");
924224090Sdougb					result = ISC_R_NOMEMORY;
925225361Sdougb					POST(result);
926224090Sdougb					/*
927224090Sdougb					 * XXX: should we continue with the
928224090Sdougb					 * available servers anyway?
929224090Sdougb					 */
930224090Sdougb					goto cleanup;
931224090Sdougb				}
932224090Sdougb
933224090Sdougb				dns_fixedname_init(&pns->fixedname);
934224090Sdougb				pns->name =
935224090Sdougb					dns_fixedname_name(&pns->fixedname);
936224090Sdougb				ISC_LINK_INIT(pns, link);
937224090Sdougb				ISC_LIST_APPEND(trans->nslist, pns, link);
938224090Sdougb				ISC_LIST_INIT(pns->servers);
939224090Sdougb
940224090Sdougb				dns_name_copy(&ns.name, pns->name, NULL);
941224090Sdougb				dns_rdata_reset(&rdata);
942224090Sdougb				dns_rdata_freestruct(&ns);
943224090Sdougb			}
944224090Sdougb		}
945224090Sdougb	}
946224090Sdougb
947224090Sdougb cleanup:
948224090Sdougb	dns_client_freeresanswer(client, &rev->answerlist);
949224090Sdougb	dns_client_destroyrestrans(&trans->resid);
950224090Sdougb	isc_event_free(&event);
951224090Sdougb
952224090Sdougb	if (!ISC_LIST_EMPTY(trans->nslist)) {
953224090Sdougb		/* Go get addresses of NSes */
954224090Sdougb		trans->current_ns = ISC_LIST_HEAD(trans->nslist);
955224090Sdougb		result = fetch_nsaddress(trans);
956224090Sdougb	} else
957224090Sdougb		result = ISC_R_FAILURE;
958224090Sdougb
959224090Sdougb	if (result == ISC_R_SUCCESS)
960224090Sdougb		return;
961224090Sdougb
962224090Sdougb	reset_probe(trans);
963224090Sdougb}
964224090Sdougb
965224090Sdougbstatic isc_result_t
966224090Sdougbprobe_domain(struct probe_trans *trans) {
967224090Sdougb	isc_result_t result;
968224090Sdougb	size_t domainlen;
969224090Sdougb	isc_buffer_t b;
970224090Sdougb	char buf[4096];	/* XXX ad hoc constant, but should be enough */
971224090Sdougb	char *cp;
972224090Sdougb
973224090Sdougb	REQUIRE(trans != NULL);
974224090Sdougb	REQUIRE(trans->inuse == ISC_FALSE);
975224090Sdougb	REQUIRE(outstanding_probes < MAX_PROBES);
976224090Sdougb
977224090Sdougb	/* Construct domain */
978224090Sdougb	cp = fgets(buf, sizeof(buf), fp);
979224090Sdougb	if (cp == NULL)
980224090Sdougb		return (ISC_R_NOMORE);
981224090Sdougb	if ((cp = strchr(buf, '\n')) != NULL) /* zap NL if any */
982224090Sdougb		*cp = '\0';
983224090Sdougb	trans->domain = isc_mem_strdup(mctx, buf);
984224090Sdougb	if (trans->domain == NULL) {
985224090Sdougb		fprintf(stderr,
986224090Sdougb			"failed to allocate memory for domain: %s", cp);
987224090Sdougb		return (ISC_R_NOMEMORY);
988224090Sdougb	}
989224090Sdougb
990224090Sdougb	/* Start getting NS for the domain */
991224090Sdougb	domainlen = strlen(buf);
992224090Sdougb	isc_buffer_init(&b, buf, domainlen);
993224090Sdougb	isc_buffer_add(&b, domainlen);
994224090Sdougb	dns_fixedname_init(&trans->fixedname);
995224090Sdougb	trans->qname = dns_fixedname_name(&trans->fixedname);
996224090Sdougb	result = dns_name_fromtext(trans->qname, &b, dns_rootname, 0, NULL);
997224090Sdougb	if (result != ISC_R_SUCCESS)
998224090Sdougb		goto cleanup;
999224090Sdougb	result = dns_client_startresolve(client, trans->qname,
1000224090Sdougb					 dns_rdataclass_in, dns_rdatatype_ns,
1001224090Sdougb					 0, probe_task, resolve_ns, trans,
1002224090Sdougb					 &trans->resid);
1003224090Sdougb	if (result != ISC_R_SUCCESS)
1004224090Sdougb		goto cleanup;
1005224090Sdougb
1006224090Sdougb	trans->inuse = ISC_TRUE;
1007224090Sdougb	outstanding_probes++;
1008224090Sdougb
1009224090Sdougb	return (ISC_R_SUCCESS);
1010224090Sdougb
1011224090Sdougb cleanup:
1012224090Sdougb	isc_mem_free(mctx, trans->domain);
1013224090Sdougb	dns_fixedname_invalidate(&trans->fixedname);
1014224090Sdougb
1015224090Sdougb	return (result);
1016224090Sdougb}
1017224090Sdougb
1018224090SdougbISC_PLATFORM_NORETURN_PRE static void
1019224090Sdougbusage(void) ISC_PLATFORM_NORETURN_POST;
1020224090Sdougb
1021224090Sdougbstatic void
1022224090Sdougbusage(void) {
1023224090Sdougb	fprintf(stderr, "usage: nsprobe [-d] [-v [-v...]] [-c cache_address] "
1024224090Sdougb		"[input_file]\n");
1025224090Sdougb
1026224090Sdougb	exit(1);
1027224090Sdougb}
1028224090Sdougb
1029224090Sdougbint
1030224090Sdougbmain(int argc, char *argv[]) {
1031224090Sdougb	int i, ch, error;
1032224090Sdougb	struct addrinfo hints, *res;
1033224090Sdougb	isc_result_t result;
1034224090Sdougb	isc_sockaddr_t sa;
1035224090Sdougb	isc_sockaddrlist_t servers;
1036224090Sdougb	isc_taskmgr_t *taskmgr = NULL;
1037224090Sdougb	isc_socketmgr_t *socketmgr = NULL;
1038224090Sdougb	isc_timermgr_t *timermgr = NULL;
1039224090Sdougb
1040224090Sdougb	while ((ch = getopt(argc, argv, "c:dhv")) != -1) {
1041224090Sdougb		switch (ch) {
1042224090Sdougb		case 'c':
1043224090Sdougb			cacheserver = optarg;
1044224090Sdougb			break;
1045224090Sdougb		case 'd':
1046224090Sdougb			debug_mode = ISC_TRUE;
1047224090Sdougb			break;
1048224090Sdougb		case 'h':
1049224090Sdougb			usage();
1050224090Sdougb			break;
1051224090Sdougb		case 'v':
1052224090Sdougb			verbose_level++;
1053224090Sdougb			break;
1054224090Sdougb		default:
1055224090Sdougb			usage();
1056224090Sdougb			break;
1057224090Sdougb		}
1058224090Sdougb	}
1059224090Sdougb
1060224090Sdougb	argc -= optind;
1061224090Sdougb	argv += optind;
1062224090Sdougb
1063224090Sdougb	/* Common set up */
1064224090Sdougb	isc_lib_register();
1065224090Sdougb	result = dns_lib_init();
1066224090Sdougb	if (result != ISC_R_SUCCESS) {
1067224090Sdougb		fprintf(stderr, "dns_lib_init failed: %d\n", result);
1068224090Sdougb		exit(1);
1069224090Sdougb	}
1070224090Sdougb
1071224090Sdougb	result = ctxs_init(&mctx, &actx, &taskmgr, &socketmgr,
1072224090Sdougb			   &timermgr);
1073224090Sdougb	if (result != ISC_R_SUCCESS) {
1074224090Sdougb		fprintf(stderr, "ctx create failed: %d\n", result);
1075224090Sdougb		exit(1);
1076224090Sdougb	}
1077224090Sdougb
1078224090Sdougb	isc_app_ctxstart(actx);
1079224090Sdougb
1080224090Sdougb	result = dns_client_createx(mctx, actx, taskmgr, socketmgr,
1081224090Sdougb				    timermgr, 0, &client);
1082224090Sdougb	if (result != ISC_R_SUCCESS) {
1083224090Sdougb		fprintf(stderr, "dns_client_createx failed: %d\n", result);
1084224090Sdougb		exit(1);
1085224090Sdougb	}
1086224090Sdougb
1087224090Sdougb	/* Set local cache server */
1088224090Sdougb	memset(&hints, 0, sizeof(hints));
1089224090Sdougb	hints.ai_family = AF_UNSPEC;
1090224090Sdougb	hints.ai_socktype = SOCK_DGRAM;
1091224090Sdougb	error = getaddrinfo(cacheserver, "53", &hints, &res);
1092224090Sdougb	if (error != 0) {
1093224090Sdougb		fprintf(stderr, "failed to convert server name (%s): %s\n",
1094224090Sdougb			cacheserver, gai_strerror(error));
1095224090Sdougb		exit(1);
1096224090Sdougb	}
1097224090Sdougb
1098224090Sdougb	if (res->ai_addrlen > sizeof(sa.type)) {
1099224090Sdougb		fprintf(stderr,
1100225361Sdougb			"assumption failure: addrlen is too long: %ld\n",
1101225361Sdougb			(long)res->ai_addrlen);
1102224090Sdougb		exit(1);
1103224090Sdougb	}
1104262706Serwin	memmove(&sa.type.sa, res->ai_addr, res->ai_addrlen);
1105224090Sdougb	sa.length = res->ai_addrlen;
1106224090Sdougb	freeaddrinfo(res);
1107224090Sdougb	ISC_LINK_INIT(&sa, link);
1108224090Sdougb	ISC_LIST_INIT(servers);
1109224090Sdougb	ISC_LIST_APPEND(servers, &sa, link);
1110224090Sdougb	result = dns_client_setservers(client, dns_rdataclass_in, NULL,
1111224090Sdougb				       &servers);
1112224090Sdougb	if (result != ISC_R_SUCCESS) {
1113224090Sdougb		fprintf(stderr, "failed to set server: %d\n", result);
1114224090Sdougb		exit(1);
1115224090Sdougb	}
1116224090Sdougb
1117224090Sdougb	/* Create the main task */
1118224090Sdougb	probe_task = NULL;
1119224090Sdougb	result = isc_task_create(taskmgr, 0, &probe_task);
1120224090Sdougb	if (result != ISC_R_SUCCESS) {
1121224090Sdougb		fprintf(stderr, "failed to create task: %d\n", result);
1122224090Sdougb		exit(1);
1123224090Sdougb	}
1124224090Sdougb
1125224090Sdougb	/* Open input file */
1126224090Sdougb	if (argc == 0)
1127224090Sdougb		fp = stdin;
1128224090Sdougb	else {
1129224090Sdougb		fp = fopen(argv[0], "r");
1130224090Sdougb		if (fp == NULL) {
1131224090Sdougb			fprintf(stderr, "failed to open input file: %s\n",
1132224090Sdougb				argv[0]);
1133224090Sdougb			exit(1);
1134224090Sdougb		}
1135224090Sdougb	}
1136224090Sdougb
1137224090Sdougb	/* Set up and start probe */
1138224090Sdougb	for (i = 0; i < MAX_PROBES; i++) {
1139224090Sdougb		probes[i].inuse = ISC_FALSE;
1140224090Sdougb		probes[i].domain = NULL;
1141224090Sdougb		dns_fixedname_init(&probes[i].fixedname);
1142224090Sdougb		probes[i].qname = NULL;
1143224090Sdougb		probes[i].qlabel = qlabels;
1144224090Sdougb		probes[i].qname_found = ISC_FALSE;
1145224090Sdougb		probes[i].resid = NULL;
1146224090Sdougb		ISC_LIST_INIT(probes[i].nslist);
1147224090Sdougb		probes[i].reqid = NULL;
1148224090Sdougb
1149224090Sdougb		probes[i].qmessage = NULL;
1150224090Sdougb		result = dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER,
1151224090Sdougb					    &probes[i].qmessage);
1152224090Sdougb		if (result == ISC_R_SUCCESS) {
1153224090Sdougb			result = dns_message_create(mctx,
1154224090Sdougb						    DNS_MESSAGE_INTENTPARSE,
1155224090Sdougb						    &probes[i].rmessage);
1156224090Sdougb		}
1157224090Sdougb		if (result != ISC_R_SUCCESS) {
1158224090Sdougb			fprintf(stderr, "initialization failure\n");
1159224090Sdougb			exit(1);
1160224090Sdougb		}
1161224090Sdougb	}
1162224090Sdougb	for (i = 0; i < MAX_PROBES; i++) {
1163224090Sdougb		result = probe_domain(&probes[i]);
1164224090Sdougb		if (result == ISC_R_NOMORE)
1165224090Sdougb			break;
1166224090Sdougb		else if (result != ISC_R_SUCCESS) {
1167224090Sdougb			fprintf(stderr, "failed to issue an initial probe\n");
1168224090Sdougb			exit(1);
1169224090Sdougb		}
1170224090Sdougb	}
1171224090Sdougb
1172224090Sdougb	/* Start event loop */
1173224090Sdougb	isc_app_ctxrun(actx);
1174224090Sdougb
1175224090Sdougb	/* Dump results */
1176224090Sdougb	printf("Per domain results (out of %lu domains):\n",
1177224090Sdougb	       number_of_domains);
1178224090Sdougb	printf("  valid: %lu\n"
1179224090Sdougb	       "  ignore: %lu\n"
1180224090Sdougb	       "  nxdomain: %lu\n"
1181224090Sdougb	       "  othererr: %lu\n"
1182224090Sdougb	       "  multiplesoa: %lu\n"
1183224090Sdougb	       "  multiplecname: %lu\n"
1184224090Sdougb	       "  brokenanswer: %lu\n"
1185224090Sdougb	       "  lame: %lu\n"
1186224090Sdougb	       "  unknown: %lu\n"
1187224090Sdougb	       "  multiple errors: %lu\n",
1188224090Sdougb	       domain_stat.valid, domain_stat.ignore, domain_stat.nxdomain,
1189224090Sdougb	       domain_stat.othererr, domain_stat.multiplesoa,
1190224090Sdougb	       domain_stat.multiplecname, domain_stat.brokenanswer,
1191224090Sdougb	       domain_stat.lame, domain_stat.unknown, multiple_error_domains);
1192224090Sdougb	printf("Per server results (out of %lu servers):\n",
1193224090Sdougb	       number_of_servers);
1194224090Sdougb	printf("  valid: %lu\n"
1195224090Sdougb	       "  ignore: %lu\n"
1196224090Sdougb	       "  nxdomain: %lu\n"
1197224090Sdougb	       "  othererr: %lu\n"
1198224090Sdougb	       "  multiplesoa: %lu\n"
1199224090Sdougb	       "  multiplecname: %lu\n"
1200224090Sdougb	       "  brokenanswer: %lu\n"
1201224090Sdougb	       "  lame: %lu\n"
1202224090Sdougb	       "  unknown: %lu\n",
1203224090Sdougb	       server_stat.valid, server_stat.ignore, server_stat.nxdomain,
1204224090Sdougb	       server_stat.othererr, server_stat.multiplesoa,
1205224090Sdougb	       server_stat.multiplecname, server_stat.brokenanswer,
1206224090Sdougb	       server_stat.lame, server_stat.unknown);
1207224090Sdougb
1208224090Sdougb	/* Cleanup */
1209224090Sdougb	for (i = 0; i < MAX_PROBES; i++) {
1210224090Sdougb		dns_message_destroy(&probes[i].qmessage);
1211224090Sdougb		dns_message_destroy(&probes[i].rmessage);
1212224090Sdougb	}
1213224090Sdougb	isc_task_detach(&probe_task);
1214224090Sdougb	dns_client_destroy(&client);
1215224090Sdougb	dns_lib_shutdown();
1216224090Sdougb	isc_app_ctxfinish(actx);
1217224090Sdougb	ctxs_destroy(&mctx, &actx, &taskmgr, &socketmgr, &timermgr);
1218224090Sdougb
1219254402Serwin	return (0);
1220224090Sdougb}
1221