diff --git a/CHANGES b/CHANGES index b36ef6355a..7b3c603bd2 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +4508. [test] Make the rrl system test more reliable on slower + machines by using mdig instead of dig. [RT #43280] + 4507. [bug] Named could incorrectly log 'allows updates by IP address, which is insecure' [RT #43432] diff --git a/bin/tests/Makefile.in b/bin/tests/Makefile.in index f7cec65dca..924667d246 100644 --- a/bin/tests/Makefile.in +++ b/bin/tests/Makefile.in @@ -13,8 +13,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: Makefile.in,v 1.145 2011/02/03 05:41:53 marka Exp $ - srcdir = @srcdir@ VPATH = @srcdir@ top_srcdir = @top_srcdir@ @@ -22,18 +20,20 @@ top_srcdir = @top_srcdir@ @BIND9_MAKE_INCLUDES@ CINCLUDES = ${DNS_INCLUDES} ${ISC_INCLUDES} ${ISCCFG_INCLUDES} \ - ${LWRES_INCLUDES} ${OMAPI_INCLUDES} + ${LWRES_INCLUDES} ${OMAPI_INCLUDES} ${BIND9_INCLUDES} CDEFINES = CWARNINGS = BACKTRACECFLAGS = @BACKTRACECFLAGS@ +BIND9LIBS = ../../lib/bind9/libbind9.@A@ DNSLIBS = ../../lib/dns/libdns.@A@ @DNS_CRYPTO_LIBS@ ISCLIBS = ../../lib/isc/libisc.@A@ @DNS_CRYPTO_LIBS@ ISCNOSYMLIBS = ../../lib/isc/libisc-nosymtbl.@A@ @DNS_CRYPTO_LIBS@ ISCCFGLIBS = ../../lib/isccfg/libisccfg.@A@ LWRESLIBS = ../../lib/lwres/liblwres.@A@ +BIND9DEPLIBS = ../../lib/bind9/libbind9.@A@ DNSDEPLIBS = ../../lib/dns/libdns.@A@ ISCDEPLIBS = ../../lib/isc/libisc.@A@ ISCDEPNOSYMLIBS = ../../lib/isc/libisc-nosymtbl.@A@ @@ -49,7 +49,7 @@ SUBDIRS = atomic db dst master mem hashes names net rbt resolver \ # cfg_test is needed for regenerating doc/misc/options # Alphabetically -TARGETS = cfg_test@EXEEXT@ wire_test@EXEEXT@ +TARGETS = cfg_test@EXEEXT@ wire_test@EXEEXT@ mdig@EXEEXT@ # All the other tests are optional and not built by default. @@ -93,7 +93,7 @@ XTARGETS = adb_test@EXEEXT@ \ zone_test@EXEEXT@ # Alphabetically -SRCS = cfg_test.c wire_test.c ${XSRCS} +SRCS = cfg_test.c mdig.c wire_test.c ${XSRCS} XSRCS = adb_test.c \ byaddr_test.c \ @@ -310,6 +310,10 @@ cfg_test@EXEEXT@: cfg_test.@O@ ${ISCCFGDEPLIBS} ${ISCDEPLIBS} ${LIBTOOL_MODE_LINK} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ cfg_test.@O@ \ ${ISCCFGLIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS} +mdig@EXEEXT@: mdig.@O@ ${BIND9DEPLIBS} ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ mdig.@O@ \ + ${BIND9LIBS} ${DNSLIBS} ${ISCLIBS} ${LIBS} + distclean:: rm -f headerdep_test.sh diff --git a/bin/tests/mdig.c b/bin/tests/mdig.c new file mode 100644 index 0000000000..b692f283f2 --- /dev/null +++ b/bin/tests/mdig.c @@ -0,0 +1,1945 @@ +/* + * Copyright (C) 2015, 2016 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/. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#define CHECK(str, x) { \ + if ((x) != ISC_R_SUCCESS) { \ + fprintf(stderr, "mdig: %s failed with %s\n", \ + (str), isc_result_totext(x)); \ + exit(-1); \ + } \ +} + +#define RUNCHECK(x) RUNTIME_CHECK((x) == ISC_R_SUCCESS) + +#define ADD_STRING(b, s) { \ + if (strlen(s) >= isc_buffer_availablelength(b)) \ + return (ISC_R_NOSPACE); \ + else \ + isc_buffer_putstr(b, s); \ +} + +#define MXNAME (DNS_NAME_MAXTEXT + 1) +#define COMMSIZE 0xffff +#define OUTPUTBUF 32767 +#define MAXPORT 0xffff +#define PORT 53 +#define MAXTIMEOUT 0xffff +#define TCPTIMEOUT 10 +#define UDPTIMEOUT 5 +#define MAXTRIES 0xffffffff + +static isc_mem_t *mctx; +static dns_requestmgr_t *requestmgr; +static const char *batchname; +static FILE *batchfp; +static isc_boolean_t have_ipv4 = ISC_FALSE; +static isc_boolean_t have_ipv6 = ISC_FALSE; +static isc_boolean_t have_src = ISC_FALSE; +static isc_boolean_t tcp_mode = ISC_FALSE; +static isc_boolean_t besteffort = ISC_TRUE; +static isc_boolean_t display_short_form = ISC_FALSE; +static isc_boolean_t display_headers = ISC_TRUE; +static isc_boolean_t display_comments = ISC_TRUE; +static isc_boolean_t display_rrcomments = ISC_TRUE; +static isc_boolean_t display_ttl = ISC_TRUE; +static isc_boolean_t display_class = ISC_TRUE; +static isc_boolean_t display_multiline = ISC_FALSE; +static isc_boolean_t display_question = ISC_TRUE; +static isc_boolean_t display_answer = ISC_TRUE; +static isc_boolean_t display_authority = ISC_TRUE; +static isc_boolean_t display_additional = ISC_TRUE; +static isc_boolean_t continue_on_error = ISC_FALSE; +static isc_uint32_t display_splitwidth = 0xffffffff; +static isc_sockaddr_t srcaddr; +static char *server; +static isc_sockaddr_t dstaddr; +static in_port_t port = 53; +static unsigned char cookie_secret[33]; +static int onfly = 0; +static char hexcookie[81]; + +struct query { + char textname[MXNAME]; /*% Name we're going to be looking up */ + isc_boolean_t ip6_int; + isc_boolean_t recurse; + isc_boolean_t have_aaonly; + isc_boolean_t have_adflag; + isc_boolean_t have_cdflag; + isc_boolean_t have_zflag; + isc_boolean_t dnssec; + isc_boolean_t expire; + isc_boolean_t send_cookie; + char *cookie; + isc_boolean_t nsid; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + isc_uint16_t udpsize; + isc_int16_t edns; + dns_ednsopt_t *ednsopts; + unsigned int ednsoptscnt; + unsigned int ednsflags; + isc_sockaddr_t *ecs_addr; + unsigned int timeout; + unsigned int udptimeout; + unsigned int udpretries; + ISC_LINK(struct query) link; +}; +static struct query default_query; +static ISC_LIST(struct query) queries; + +#define EDNSOPTS 100U +/*% opcode text */ +static const char * const opcodetext[] = { + "QUERY", + "IQUERY", + "STATUS", + "RESERVED3", + "NOTIFY", + "UPDATE", + "RESERVED6", + "RESERVED7", + "RESERVED8", + "RESERVED9", + "RESERVED10", + "RESERVED11", + "RESERVED12", + "RESERVED13", + "RESERVED14", + "RESERVED15" +}; + +/*% return code text */ +static const char * const rcodetext[] = { + "NOERROR", + "FORMERR", + "SERVFAIL", + "NXDOMAIN", + "NOTIMP", + "REFUSED", + "YXDOMAIN", + "YXRRSET", + "NXRRSET", + "NOTAUTH", + "NOTZONE", + "RESERVED11", + "RESERVED12", + "RESERVED13", + "RESERVED14", + "RESERVED15", + "BADVERS" +}; + +/*% safe rcodetext[] */ +static char * +rcode_totext(dns_rcode_t rcode) +{ + static char buf[sizeof("?65535")]; + union { + const char *consttext; + char *deconsttext; + } totext; + + if (rcode >= (sizeof(rcodetext)/sizeof(rcodetext[0]))) { + snprintf(buf, sizeof(buf), "?%u", rcode); + totext.deconsttext = buf; + } else + totext.consttext = rcodetext[rcode]; + return totext.deconsttext; +} + +/* receive response event handler */ +static void +recvresponse(isc_task_t *task, isc_event_t *event) { + dns_requestevent_t *reqev = (dns_requestevent_t *)event; + isc_result_t result; + dns_message_t *query = NULL, *response = NULL; + unsigned int parseflags = 0; + isc_buffer_t *buf = NULL; + unsigned int len = OUTPUTBUF; + dns_master_style_t *style = NULL; + unsigned int styleflags = 0; + dns_messagetextflag_t flags; + + UNUSED(task); + + REQUIRE(reqev != NULL); + query = reqev->ev_arg; + + if (reqev->result != ISC_R_SUCCESS) { + fprintf(stderr, "response failed with %s\n", + isc_result_totext(reqev->result)); + if (continue_on_error) + goto cleanup; + else + exit(-1); + } + + result = dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &response); + CHECK("dns_message_create", result); + + parseflags |= DNS_MESSAGEPARSE_PRESERVEORDER; + if (besteffort) { + parseflags |= DNS_MESSAGEPARSE_BESTEFFORT; + parseflags |= DNS_MESSAGEPARSE_IGNORETRUNCATION; + } + result = dns_request_getresponse(reqev->request, response, parseflags); + CHECK("dns_request_getresponse", result); + + styleflags |= DNS_STYLEFLAG_REL_OWNER; + if (display_comments) + styleflags |= DNS_STYLEFLAG_COMMENT; + if (display_rrcomments) + styleflags |= DNS_STYLEFLAG_RRCOMMENT; + if (!display_ttl) + styleflags |= DNS_STYLEFLAG_NO_TTL; + if (!display_class) + styleflags |= DNS_STYLEFLAG_NO_CLASS; + if (display_multiline) { + styleflags |= DNS_STYLEFLAG_OMIT_OWNER; + styleflags |= DNS_STYLEFLAG_OMIT_CLASS; + styleflags |= DNS_STYLEFLAG_REL_DATA; + styleflags |= DNS_STYLEFLAG_OMIT_TTL; + styleflags |= DNS_STYLEFLAG_TTL; + styleflags |= DNS_STYLEFLAG_MULTILINE; + styleflags |= DNS_STYLEFLAG_COMMENT; + styleflags |= DNS_STYLEFLAG_RRCOMMENT; + } + if (display_multiline || (!display_ttl && !display_class)) + result = dns_master_stylecreate2(&style, styleflags, + 24, 24, 24, 32, 80, 8, + display_splitwidth, mctx); + else if (!display_ttl || !display_class) + result = dns_master_stylecreate2(&style, styleflags, + 24, 24, 32, 40, 80, 8, + display_splitwidth, mctx); + else + result = dns_master_stylecreate2(&style, styleflags, + 24, 32, 40, 48, 80, 8, + display_splitwidth, mctx); + CHECK("dns_master_stylecreate2", result); + + flags = 0; + if (!display_headers) { + flags |= DNS_MESSAGETEXTFLAG_NOHEADERS; + flags |= DNS_MESSAGETEXTFLAG_NOCOMMENTS; + } + if (!display_comments) + flags |= DNS_MESSAGETEXTFLAG_NOCOMMENTS; + + result = isc_buffer_allocate(mctx, &buf, len); + CHECK("isc_buffer_allocate", result); + + if (display_comments && !display_short_form) { + printf(";; Got answer:\n"); + + if (display_headers) { + printf(";; ->>HEADER<<- opcode: %s, status: %s, " + "id: %u\n", + opcodetext[response->opcode], + rcode_totext(response->rcode), + response->id); + printf(";; flags:"); + if ((response->flags & DNS_MESSAGEFLAG_QR) != 0) + printf(" qr"); + if ((response->flags & DNS_MESSAGEFLAG_AA) != 0) + printf(" aa"); + if ((response->flags & DNS_MESSAGEFLAG_TC) != 0) + printf(" tc"); + if ((response->flags & DNS_MESSAGEFLAG_RD) != 0) + printf(" rd"); + if ((response->flags & DNS_MESSAGEFLAG_RA) != 0) + printf(" ra"); + if ((response->flags & DNS_MESSAGEFLAG_AD) != 0) + printf(" ad"); + if ((response->flags & DNS_MESSAGEFLAG_CD) != 0) + printf(" cd"); + if ((response->flags & 0x0040U) != 0) + printf("; MBZ: 0x4"); + + printf("; QUERY: %u, ANSWER: %u, " + "AUTHORITY: %u, ADDITIONAL: %u\n", + response->counts[DNS_SECTION_QUESTION], + response->counts[DNS_SECTION_ANSWER], + response->counts[DNS_SECTION_AUTHORITY], + response->counts[DNS_SECTION_ADDITIONAL]); + + if ((response->flags & DNS_MESSAGEFLAG_RD) != 0 && + (response->flags & DNS_MESSAGEFLAG_RA) == 0) + printf(";; WARNING: recursion requested " + "but not available\n"); + } + } + +repopulate_buffer: + + if (display_comments && display_headers && !display_short_form) { + result = dns_message_pseudosectiontotext(response, + DNS_PSEUDOSECTION_OPT, + style, flags, buf); + if (result == ISC_R_NOSPACE) { +buftoosmall: + len += OUTPUTBUF; + isc_buffer_free(&buf); + result = isc_buffer_allocate(mctx, &buf, len); + if (result == ISC_R_SUCCESS) + goto repopulate_buffer; + else + goto cleanup; + } + CHECK("dns_message_pseudosectiontotext", result); + } + + if (display_question && display_headers && !display_short_form) { + result = dns_message_sectiontotext(response, + DNS_SECTION_QUESTION, + style, flags, buf); + if (result == ISC_R_NOSPACE) + goto buftoosmall; + CHECK("dns_message_sectiontotext", result); + } + + if (display_answer && !display_short_form) { + result = dns_message_sectiontotext(response, + DNS_SECTION_ANSWER, + style, flags, buf); + if (result == ISC_R_NOSPACE) + goto buftoosmall; + CHECK("dns_message_sectiontotext", result); + } else if (display_answer) { + dns_name_t *name; + dns_rdataset_t *rdataset; + isc_result_t loopresult; + dns_name_t empty_name; + dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned int answerstyleflags = 0; + + dns_name_init(&empty_name, NULL); + result = dns_message_firstname(response, DNS_SECTION_ANSWER); + if (result != ISC_R_NOMORE) + CHECK("dns_message_firstname", result); + + for (;;) { + if (result == ISC_R_NOMORE) + break; + CHECK("dns_message_nextname", result); + name = NULL; + dns_message_currentname(response, + DNS_SECTION_ANSWER, + &name); + + for (rdataset = ISC_LIST_HEAD(name->list); + rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) { + loopresult = dns_rdataset_first(rdataset); + while (loopresult == ISC_R_SUCCESS) { + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tofmttext( + &rdata, + NULL, + answerstyleflags, + 0, 60, " ", buf); + if (result == ISC_R_NOSPACE) + goto buftoosmall; + CHECK("dns_rdata_tofmttext", result); + loopresult = + dns_rdataset_next(rdataset); + dns_rdata_reset(&rdata); + if (strlen("\n") >= + isc_buffer_availablelength(buf)) + goto buftoosmall; + isc_buffer_putstr(buf, "\n"); + } + } + result = dns_message_nextname(response, + DNS_SECTION_ANSWER); + } + } + + if (display_authority && !display_short_form) { + result = dns_message_sectiontotext(response, + DNS_SECTION_AUTHORITY, + style, flags, buf); + if (result == ISC_R_NOSPACE) + goto buftoosmall; + CHECK("dns_message_sectiontotext", result); + } + + if (display_additional && !display_short_form) { + result = dns_message_sectiontotext(response, + DNS_SECTION_ADDITIONAL, + style, flags, buf); + if (result == ISC_R_NOSPACE) + goto buftoosmall; + CHECK("dns_message_sectiontotext", result); + } + + + if (display_additional && !display_short_form && display_headers) { + /* + * Only print the signature on the first record. + */ + result = + dns_message_pseudosectiontotext(response, + DNS_PSEUDOSECTION_TSIG, + style, flags, buf); + if (result == ISC_R_NOSPACE) + goto buftoosmall; + CHECK("dns_message_pseudosectiontotext", result); + result = + dns_message_pseudosectiontotext(response, + DNS_PSEUDOSECTION_SIG0, + style, flags, buf); + if (result == ISC_R_NOSPACE) + goto buftoosmall; + CHECK("dns_message_pseudosectiontotext", result); + } + + if (display_headers && display_comments && !display_short_form) + printf("\n"); + + printf("%.*s", (int)isc_buffer_usedlength(buf), + (char *)isc_buffer_base(buf)); + isc_buffer_free(&buf); + +cleanup: + fflush(stdout); + if (style != NULL) + dns_master_styledestroy(&style, mctx); + if (query != NULL) + dns_message_destroy(&query); + if (response != NULL) + dns_message_destroy(&response); + dns_request_destroy(&reqev->request); + isc_event_free(&event); + + if (--onfly == 0) + isc_app_shutdown(); + return; +} + +/*% + * Add EDNS0 option record to a message. Currently, the only supported + * options are UDP buffer size, the DO bit, and EDNS options + * (e.g., NSID, COOKIE, client-subnet) + */ +static void +add_opt(dns_message_t *msg, isc_uint16_t udpsize, isc_uint16_t edns, + unsigned int flags, dns_ednsopt_t *ednsopts, size_t count) +{ + dns_rdataset_t *rdataset = NULL; + isc_result_t result; + + result = dns_message_buildopt(msg, &rdataset, edns, udpsize, flags, + ednsopts, count); + CHECK("dns_message_buildopt", result); + result = dns_message_setopt(msg, rdataset); + CHECK("dns_message_setopt", result); +} + +static void +compute_cookie(unsigned char *cookie, size_t len) { + /* XXXMPA need to fix, should be per server. */ + INSIST(len >= 8U); + memmove(cookie, cookie_secret, 8); +} + +static isc_result_t +sendquery(struct query *query, isc_task_t *task) +{ + dns_request_t *request; + dns_message_t *message; + dns_name_t *qname; + dns_rdataset_t *qrdataset; + isc_result_t result; + dns_fixedname_t queryname; + isc_buffer_t buf; + unsigned int options; + + onfly++; + + dns_fixedname_init(&queryname); + isc_buffer_init(&buf, query->textname, strlen(query->textname)); + isc_buffer_add(&buf, strlen(query->textname)); + result = dns_name_fromtext(dns_fixedname_name(&queryname), &buf, + dns_rootname, 0, NULL); + CHECK("dns_name_fromtext", result); + + message = NULL; + result = dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER, &message); + CHECK("dns_message_create", result); + + message->opcode = dns_opcode_query; + if (query->recurse) + message->flags |= DNS_MESSAGEFLAG_RD; + if (query->have_aaonly) + message->flags |= DNS_MESSAGEFLAG_AA; + if (query->have_adflag) + message->flags |= DNS_MESSAGEFLAG_AD; + if (query->have_cdflag) + message->flags |= DNS_MESSAGEFLAG_CD; + if (query->have_zflag) + message->flags |= 0x0040U; + message->rdclass = query->rdclass; + message->id = (unsigned short)(random() & 0xFFFF); + + qname = NULL; + result = dns_message_gettempname(message, &qname); + CHECK("dns_message_gettempname", result); + + qrdataset = NULL; + result = dns_message_gettemprdataset(message, &qrdataset); + CHECK("dns_message_gettemprdataset", result); + + dns_name_init(qname, NULL); + dns_name_clone(dns_fixedname_name(&queryname), qname); + dns_rdataset_makequestion(qrdataset, query->rdclass, + query->rdtype); + ISC_LIST_APPEND(qname->list, qrdataset, link); + dns_message_addname(message, qname, DNS_SECTION_QUESTION); + + if (query->udpsize > 0 || query->dnssec || + query->edns > -1 || query->ecs_addr != NULL) + { + dns_ednsopt_t opts[EDNSOPTS + DNS_EDNSOPTIONS]; + unsigned int flags; + int i = 0; + char ecsbuf[20]; + unsigned char cookie[40]; + + if (query->udpsize == 0) + query->udpsize = 4096; + if (query->edns < 0) + query->edns = 0; + + if (query->nsid) { + INSIST(i < DNS_EDNSOPTIONS); + opts[i].code = DNS_OPT_NSID; + opts[i].length = 0; + opts[i].value = NULL; + i++; + } + + if (query->ecs_addr != NULL) { + isc_uint8_t addr[16], family; + isc_uint32_t plen; + struct sockaddr *sa; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + size_t addrl; + isc_buffer_t b; + + sa = &query->ecs_addr->type.sa; + plen = query->ecs_addr->length; + + /* Round up prefix len to a multiple of 8 */ + addrl = (plen + 7) / 8; + + INSIST(i < DNS_EDNSOPTIONS); + opts[i].code = DNS_OPT_CLIENT_SUBNET; + opts[i].length = (isc_uint16_t) addrl + 4; + CHECK("isc_buffer_allocate", result); + isc_buffer_init(&b, ecsbuf, sizeof(ecsbuf)); + if (sa->sa_family == AF_INET) { + family = 1; + sin = (struct sockaddr_in *) sa; + memmove(addr, &sin->sin_addr, 4); + if ((plen % 8) != 0) + addr[addrl-1] &= + ~0U << (8 - (plen % 8)); + } else { + family = 2; + sin6 = (struct sockaddr_in6 *) sa; + memmove(addr, &sin6->sin6_addr, 16); + } + + /* Mask off last address byte */ + if (addrl > 0 && (plen % 8) != 0) + addr[addrl - 1] &= ~0U << (8 - (plen % 8)); + + /* family */ + isc_buffer_putuint16(&b, family); + /* source prefix-length */ + isc_buffer_putuint8(&b, plen); + /* scope prefix-length */ + isc_buffer_putuint8(&b, 0); + /* address */ + if (addrl > 0) + isc_buffer_putmem(&b, addr, + (unsigned)addrl); + + opts[i].value = (isc_uint8_t *) ecsbuf; + i++; + } + + if (query->send_cookie) { + INSIST(i < DNS_EDNSOPTIONS); + opts[i].code = DNS_OPT_COOKIE; + if (query->cookie != NULL) { + isc_buffer_t b; + + isc_buffer_init(&b, cookie, sizeof(cookie)); + result = isc_hex_decodestring(query->cookie, + &b); + CHECK("isc_hex_decodestring", result); + opts[i].value = isc_buffer_base(&b); + opts[i].length = isc_buffer_usedlength(&b); + } else { + compute_cookie(cookie, 8); + opts[i].length = 8; + opts[i].value = cookie; + } + i++; + } + + if (query->expire) { + INSIST(i < DNS_EDNSOPTIONS); + opts[i].code = DNS_OPT_EXPIRE; + opts[i].length = 0; + opts[i].value = NULL; + i++; + } + + if (query->ednsoptscnt != 0) { + memmove(&opts[i], query->ednsopts, + sizeof(dns_ednsopt_t) * query->ednsoptscnt); + i += query->ednsoptscnt; + } + + flags = query->ednsflags; + flags &= ~DNS_MESSAGEEXTFLAG_DO; + if (query->dnssec) + flags |= DNS_MESSAGEEXTFLAG_DO; + add_opt(message, query->udpsize, query->edns, flags, opts, i); + } + + options = 0; + if (tcp_mode) + options |= DNS_REQUESTOPT_TCP; + request = NULL; + result = dns_request_createvia3(requestmgr, message, + have_src ? &srcaddr : NULL, &dstaddr, + options, NULL, + query->timeout, query->udptimeout, + query->udpretries, task, + recvresponse, message, &request); + CHECK("dns_request_createvia4", result); + + return ISC_R_SUCCESS; +} + +static void +sendqueries(isc_task_t *task, isc_event_t *event) +{ + struct query *query = (struct query *)event->ev_arg; + + isc_event_free(&event); + + while (query != NULL) { + struct query *next = ISC_LIST_NEXT(query, link); + + sendquery(query, task); + query = next; + } + + if (onfly == 0) + isc_app_shutdown(); + return; +} + +ISC_PLATFORM_NORETURN_PRE static void +usage(void) ISC_PLATFORM_NORETURN_POST; + +static void +usage(void) { + fputs("Usage: mdig @server {global-opt} host\n" + " {local-opt} [ host {local-opt} [...]]\n", + stderr); + fputs("\nUse \"mdig -h\" (or \"mdig -h | more\") " + "for complete list of options\n", + stderr); + exit(1); +} + +/*% help */ +static void +help(void) { + fputs("Usage: mdig @server {global-opt} host\n" + " {local-opt} [ host {local-opt} [...]]\n", + stdout); + fputs( +"Where:\n" +" anywhere opt is one of:\n" +" -f filename (batch mode)\n" +" -h (print help and exit)\n" +" -v (print version and exit)\n" +" global opt is one of:\n" +" -4 (use IPv4 query transport only)\n" +" -6 (use IPv6 query transport only)\n" +" -b address[#port] (bind to source address/port)\n" +" -p port (specify port number)\n" +" +[no]vc (TCP mode)\n" +" +[no]tcp (TCP mode, alternate syntax)\n" +" +[no]besteffort (Try to parse even illegal messages)\n" +" +[no]cl (Control display of class in records)\n" +" +[no]comments (Control display of comment lines)\n" +" +[no]rrcomments (Control display of per-record " + "comments)\n" +" +[no]crypto (Control display of cryptographic " + "fields in records)\n" +" +[no]question (Control display of question)\n" +" +[no]answer (Control display of answer)\n" +" +[no]authority (Control display of authority)\n" +" +[no]additional (Control display of additional)\n" +" +[no]short (Disable everything except short\n" +" form of answer)\n" +" +[no]ttlid (Control display of ttls in records)\n" +" +[no]all (Set or clear all display flags)\n" +" +[no]multiline (Print records in an expanded format)\n" +" +[no]split=## (Split hex/base64 fields into chunks)\n" +" local opt is one of:\n" +" -c class (specify query class)\n" +" -t type (specify query type)\n" +" -i (use IP6.INT for IPv6 reverse lookups)\n" +" -x dot-notation (shortcut for reverse lookups)\n" +" +timeout=### (Set query timeout) [UDP=5,TCP=10]\n" +" +udptimeout=### (Set timeout before UDP retry)\n" +" +tries=### (Set number of UDP attempts) [3]\n" +" +retry=### (Set number of UDP retries) [2]\n" +" +bufsize=### (Set EDNS0 Max UDP packet size)\n" +" +subnet=addr (Set edns-client-subnet option)\n" +" +[no]edns[=###] (Set EDNS version) [0]\n" +" +ednsflags=### (Set EDNS flag bits)\n" +" +ednsopt=###[:value] (Send specified EDNS option)\n" +" +noednsopt (Clear list of +ednsopt options)\n" +" +[no]recurse (Recursive mode)\n" +" +[no]aaonly (Set AA flag in query (+[no]aaflag))\n" +" +[no]adflag (Set AD flag in query)\n" +" +[no]cdflag (Set CD flag in query)\n" +" +[no]zflag (Set Z flag in query)\n" +" +[no]dnssec (Request DNSSEC records)\n" +" +[no]expire (Request time to expire)\n" +" +[no]cookie[=###] (Send a COOKIE option)\n" +" +[no]nsid (Request Name Server ID)\n", + stdout); +} + +static char * +next_token(char **stringp, const char *delim) { + char *res; + + do { + res = strsep(stringp, delim); + if (res == NULL) + break; + } while (*res == '\0'); + return (res); +} + +ISC_PLATFORM_NORETURN_PRE static void +fatal(const char *format, ...) +ISC_FORMAT_PRINTF(1, 2) ISC_PLATFORM_NORETURN_POST; + +static void +fatal(const char *format, ...) { + va_list args; + + fflush(stdout); + fprintf(stderr, "mdig: "); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + exit(-2); +} + +static isc_result_t +parse_uint_helper(isc_uint32_t *uip, const char *value, isc_uint32_t max, + const char *desc, int base) { + isc_uint32_t n; + isc_result_t result = isc_parse_uint32(&n, value, base); + if (result == ISC_R_SUCCESS && n > max) + result = ISC_R_RANGE; + if (result != ISC_R_SUCCESS) { + printf("invalid %s '%s': %s\n", desc, + value, isc_result_totext(result)); + return (result); + } + *uip = n; + return (ISC_R_SUCCESS); +} + +static isc_result_t +parse_uint(isc_uint32_t *uip, const char *value, isc_uint32_t max, + const char *desc) { + return (parse_uint_helper(uip, value, max, desc, 10)); +} + +static isc_result_t +parse_xint(isc_uint32_t *uip, const char *value, isc_uint32_t max, + const char *desc) { + return (parse_uint_helper(uip, value, max, desc, 0)); +} + +static dns_ednsopt_t ednsopts[EDNSOPTS]; +static unsigned char ednsoptscnt = 0; + +static void +save_opt(struct query *query, char *code, char *value) { + isc_uint32_t num; + isc_buffer_t b; + isc_result_t result; + + if (ednsoptscnt == EDNSOPTS) + fatal("too many ednsopts"); + + result = parse_uint(&num, code, 65535, "ednsopt"); + if (result != ISC_R_SUCCESS) + fatal("bad edns code point: %s", code); + + ednsopts[ednsoptscnt].code = num; + ednsopts[ednsoptscnt].length = 0; + ednsopts[ednsoptscnt].value = NULL; + + if (value != NULL) { + char *buf; + buf = isc_mem_allocate(mctx, strlen(value)/2 + 1); + if (buf == NULL) + fatal("out of memory"); + isc_buffer_init(&b, buf, strlen(value)/2 + 1); + result = isc_hex_decodestring(value, &b); + CHECK("isc_hex_decodestring", result); + ednsopts[ednsoptscnt].value = isc_buffer_base(&b); + ednsopts[ednsoptscnt].length = isc_buffer_usedlength(&b); + } + + if (query->ednsoptscnt == 0) + query->ednsopts = &ednsopts[ednsoptscnt]; + query->ednsoptscnt++; + ednsoptscnt++; +} + +static isc_result_t +parse_netprefix(isc_sockaddr_t **sap, const char *value) { + isc_result_t result = ISC_R_SUCCESS; + isc_sockaddr_t *sa = NULL; + struct in_addr in4; + struct in6_addr in6; + isc_uint32_t netmask = 0xffffffff; + char *slash = NULL; + isc_boolean_t parsed = ISC_FALSE; + char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:XXX.XXX.XXX.XXX/128")]; + + if (strlcpy(buf, value, sizeof(buf)) >= sizeof(buf)) + fatal("invalid prefix '%s'\n", value); + + slash = strchr(buf, '/'); + if (slash != NULL) { + *slash = '\0'; + result = isc_parse_uint32(&netmask, slash + 1, 10); + if (result != ISC_R_SUCCESS) { + fatal("invalid prefix length in '%s': %s\n", + value, isc_result_totext(result)); + } + } else if (strcmp(value, "0") == 0) { + netmask = 0; + } + + sa = isc_mem_allocate(mctx, sizeof(*sa)); + if (sa == NULL) + fatal("out of memory"); + if (inet_pton(AF_INET6, buf, &in6) == 1) { + parsed = ISC_TRUE; + isc_sockaddr_fromin6(sa, &in6, 0); + if (netmask > 128) + netmask = 128; + } else if (inet_pton(AF_INET, buf, &in4) == 1) { + parsed = ISC_TRUE; + isc_sockaddr_fromin(sa, &in4, 0); + if (netmask > 32) + netmask = 32; + } else if (netmask != 0xffffffff) { + int i; + + for (i = 0; i < 3 && strlen(buf) < sizeof(buf) - 2; i++) { + strlcat(buf, ".0", sizeof(buf)); + if (inet_pton(AF_INET, buf, &in4) == 1) { + parsed = ISC_TRUE; + isc_sockaddr_fromin(sa, &in4, 0); + break; + } + } + + if (netmask > 32) + netmask = 32; + } + + if (!parsed) + fatal("invalid address '%s'", value); + + sa->length = netmask; + *sap = sa; + + return (ISC_R_SUCCESS); +} + +/*% + * Append 'len' bytes of 'text' at '*p', failing with + * ISC_R_NOSPACE if that would advance p past 'end'. + */ +static isc_result_t +append(const char *text, int len, char **p, char *end) { + if (len > end - *p) + return (ISC_R_NOSPACE); + memmove(*p, text, len); + *p += len; + return (ISC_R_SUCCESS); +} + +static isc_result_t +reverse_octets(const char *in, char **p, char *end) { + const char *dot = strchr(in, '.'); + int len; + if (dot != NULL) { + isc_result_t result; + result = reverse_octets(dot + 1, p, end); + CHECK("reverse_octets", result); + result = append(".", 1, p, end); + CHECK("append", result); + len = (int)(dot - in); + } else { + len = strlen(in); + } + return (append(in, len, p, end)); +} + +static void +get_reverse(char *reverse, size_t len, const char *value, + isc_boolean_t ip6_int) +{ + int r; + isc_result_t result; + isc_netaddr_t addr; + + addr.family = AF_INET6; + r = inet_pton(AF_INET6, value, &addr.type.in6); + if (r > 0) { + /* This is a valid IPv6 address. */ + dns_fixedname_t fname; + dns_name_t *name; + unsigned int options = 0; + + if (ip6_int) + options |= DNS_BYADDROPT_IPV6INT; + dns_fixedname_init(&fname); + name = dns_fixedname_name(&fname); + result = dns_byaddr_createptrname2(&addr, options, name); + CHECK("dns_byaddr_createptrname2", result); + dns_name_format(name, reverse, (unsigned int)len); + return; + } else { + /* + * Not a valid IPv6 address. Assume IPv4. + * Construct the in-addr.arpa name by blindly + * reversing octets whether or not they look like + * integers, so that this can be used for RFC2317 + * names and such. + */ + char *p = reverse; + char *end = reverse + len; + result = reverse_octets(value, &p, end); + CHECK("reverse_octets", result); + /* Append .in-addr.arpa. and a terminating NUL. */ + result = append(".in-addr.arpa.", 15, &p, end); + CHECK("append", result); + return; + } +} + +/*% + * We're not using isc_commandline_parse() here since the command line + * syntax of mdig is quite a bit different from that which can be described + * by that routine. + * XXX doc options + */ + +static void +plus_option(char *option, struct query *query, isc_boolean_t global) +{ + isc_result_t result; + char option_store[256]; + char *cmd, *value, *ptr, *code; + isc_uint32_t num; + isc_boolean_t state = ISC_TRUE; + size_t n; + + strncpy(option_store, option, sizeof(option_store)); + option_store[sizeof(option_store) - 1] = 0; + ptr = option_store; + cmd = next_token(&ptr, "="); + if (cmd == NULL) { + printf(";; Invalid option %s\n", option_store); + return; + } + value = ptr; + if (strncasecmp(cmd, "no", 2) == 0) { + cmd += 2; + state = ISC_FALSE; + } + +#define FULLCHECK(A) \ + do { \ + size_t _l = strlen(cmd); \ + if (_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) \ + goto invalid_option; \ + } while (0) +#define FULLCHECK2(A, B) \ + do { \ + size_t _l = strlen(cmd); \ + if ((_l >= sizeof(A) || strncasecmp(cmd, A, _l) != 0) && \ + (_l >= sizeof(B) || strncasecmp(cmd, B, _l) != 0)) \ + goto invalid_option; \ + } while (0) +#define GLOBAL() \ + do { \ + if (!global) \ + goto global_option; \ + } while (0) + + switch (cmd[0]) { + case 'a': + switch (cmd[1]) { + case 'a': /* aaonly / aaflag */ + FULLCHECK2("aaonly", "aaflag"); + query->have_aaonly = state; + break; + case 'd': + switch (cmd[2]) { + case 'd': /* additional */ + FULLCHECK("additional"); + display_additional = state; + break; + case 'f': /* adflag */ + case '\0': /* +ad is a synonym for +adflag */ + FULLCHECK("adflag"); + query->have_adflag = state; + break; + default: + goto invalid_option; + } + break; + case 'l': /* all */ + FULLCHECK("all"); + GLOBAL(); + display_question = state; + display_answer = state; + display_authority = state; + display_additional = state; + display_comments = state; + display_rrcomments = state; + break; + case 'n': /* answer */ + FULLCHECK("answer"); + GLOBAL(); + display_answer = state; + break; + case 'u': /* authority */ + FULLCHECK("authority"); + GLOBAL(); + display_authority = state; + break; + default: + goto invalid_option; + } + break; + case 'b': + switch (cmd[1]) { + case 'e':/* besteffort */ + FULLCHECK("besteffort"); + GLOBAL(); + besteffort = state; + break; + case 'u':/* bufsize */ + FULLCHECK("bufsize"); + if (value == NULL) + goto need_value; + if (!state) + goto invalid_option; + result = parse_uint(&num, value, COMMSIZE, + "buffer size"); + CHECK("parse_uint(buffer size)", result); + query->udpsize = num; + break; + default: + goto invalid_option; + } + break; + case 'c': + switch (cmd[1]) { + case 'd':/* cdflag */ + switch (cmd[2]) { + case 'f': /* cdflag */ + case '\0': /* +cd is a synonym for +cdflag */ + FULLCHECK("cdflag"); + query->have_cdflag = state; + break; + default: + goto invalid_option; + } + break; + case 'l': /* cl */ + FULLCHECK("cl"); + GLOBAL(); + display_class = state; + break; + case 'o': /* comments */ + switch (cmd[2]) { + case 'm': + FULLCHECK("comments"); + GLOBAL(); + display_comments = state; + break; + case 'n': + FULLCHECK("continue"); + GLOBAL(); + continue_on_error = state; + break; + case 'o': + FULLCHECK("cookie"); + if (state && query->edns == -1) + query->edns = 0; + query->send_cookie = state; + if (value != NULL) { + n = strlcpy(hexcookie, value, + sizeof(hexcookie)); + if (n >= sizeof(hexcookie)) + fatal("COOKIE data too large"); + query->cookie = hexcookie; + } else + query->cookie = NULL; + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + case 'd': + switch (cmd[1]) { + case 'n': /* dnssec */ + FULLCHECK("dnssec"); + if (state && query->edns == -1) + query->edns = 0; + query->dnssec = state; + break; + default: + goto invalid_option; + } + break; + case 'e': + switch (cmd[1]) { + case 'd': + switch(cmd[2]) { + case 'n': + switch (cmd[3]) { + case 's': + switch (cmd[4]) { + case 0: + FULLCHECK("edns"); + if (!state) { + query->edns = -1; + break; + } + if (value == NULL) { + query->edns = 0; + break; + } + result = parse_uint(&num, + value, + 255, + "edns"); + CHECK("parse_uint(edns)", + result); + query->edns = num; + break; + case 'f': + FULLCHECK("ednsflags"); + if (!state) { + query->ednsflags = 0; + break; + } + if (value == NULL) { + query->ednsflags = 0; + break; + } + result = parse_xint(&num, + value, + 0xffff, + "ednsflags"); + CHECK("parse_xint(ednsflags)", + result); + query->ednsflags = num; + break; + case 'o': + FULLCHECK("ednsopt"); + if (!state) { + query->ednsoptscnt = 0; + break; + } + if (value == NULL) + fatal("ednsopt no " + "code point " + "specified"); + code = next_token(&value, ":"); + save_opt(query, code, value); + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + case 'x': + FULLCHECK("expire"); + query->expire = state; + break; + default: + goto invalid_option; + } + break; + case 'm': /* multiline */ + FULLCHECK("multiline"); + GLOBAL(); + display_multiline = state; + break; + case 'n': + FULLCHECK("nsid"); + if (state && query->edns == -1) + query->edns = 0; + query->nsid = state; + break; + case 'q': + FULLCHECK("question"); + GLOBAL(); + display_question = state; + break; + case 'r': + switch (cmd[1]) { + case 'e': + switch (cmd[2]) { + case 'c': /* recurse */ + FULLCHECK("recurse"); + query->recurse = state; + break; + case 't': /* retry / retries */ + FULLCHECK2("retry", "retries"); + if (value == NULL) + goto need_value; + if (!state) + goto invalid_option; + result = parse_uint(&query->udpretries, + value, + MAXTRIES - 1, + "udpretries"); + CHECK("parse_uint(udpretries)", result); + query->udpretries++; + break; + default: + goto invalid_option; + } + break; + case 'r': + FULLCHECK("rrcomments"); + GLOBAL(); + display_rrcomments = state; + break; + default: + goto invalid_option; + } + break; + case 's': + switch (cmd[1]) { + case 'h': + FULLCHECK("short"); + GLOBAL(); + display_short_form = state; + if (state) { + display_question = ISC_FALSE; + display_answer = ISC_TRUE; + display_authority = ISC_FALSE; + display_additional = ISC_FALSE; + display_comments = ISC_FALSE; + display_rrcomments = ISC_FALSE; + } + break; + case 'p': /* split */ + FULLCHECK("split"); + GLOBAL(); + if (value != NULL && !state) + goto invalid_option; + if (!state) { + display_splitwidth = 0; + break; + } else if (value == NULL) + break; + + result = parse_uint(&display_splitwidth, value, + 1023, "split"); + if (display_splitwidth % 4 != 0) { + display_splitwidth = + ((display_splitwidth + 3) / 4) * 4; + fprintf(stderr, ";; Warning, split must be " + "a multiple of 4; adjusting " + "to %d\n", + display_splitwidth); + } + /* + * There is an adjustment done in the + * totext_() functions which causes + * splitwidth to shrink. This is okay when we're + * using the default width but incorrect in this + * case, so we correct for it + */ + if (display_splitwidth) + display_splitwidth += 3; + CHECK("parse_uint(split)", result); + break; + case 'u': /* subnet */ + FULLCHECK("subnet"); + if (state && value == NULL) + goto need_value; + if (!state) { + if (query->ecs_addr != NULL) { + isc_mem_free(mctx, query->ecs_addr); + query->ecs_addr = NULL; + } + break; + } + if (query->edns == -1) + query->edns = 0; + result = parse_netprefix(&query->ecs_addr, value); + CHECK("parse_netprefix", result); + break; + default: + goto invalid_option; + } + break; + case 't': + switch (cmd[1]) { + case 'c': /* tcp */ + FULLCHECK("tcp"); + GLOBAL(); + tcp_mode = state; + break; + case 'i': /* timeout */ + FULLCHECK("timeout"); + if (value == NULL) + goto need_value; + if (!state) + goto invalid_option; + result = parse_uint(&query->timeout, value, + MAXTIMEOUT, "timeout"); + CHECK("parse_uint(timeout)", result); + if (query->timeout == 0) + query->timeout = 1; + break; + case 'r': + FULLCHECK("tries"); + if (value == NULL) + goto need_value; + if (!state) + goto invalid_option; + result = parse_uint(&query->udpretries, value, + MAXTRIES, "udpretries"); + CHECK("parse_uint(udpretries)", result); + if (query->udpretries == 0) + query->udpretries = 1; + break; + case 't': + switch (cmd[2]) { + case 'l': + switch (cmd[3]) { + case 0: + case 'i': /* ttlid */ + FULLCHECK2("ttl", "ttlid"); + GLOBAL(); + display_ttl = state; + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + default: + goto invalid_option; + } + break; + case 'u': + switch (cmd[1]) { + case 'd': + FULLCHECK("udptimeout"); + if (value == NULL) + goto need_value; + if (!state) + goto invalid_option; + result = parse_uint(&query->udptimeout, value, + MAXTIMEOUT, "udptimeout"); + CHECK("parse_uint(udptimeout)", result); + break; + default: + goto invalid_option; + } + break; + case 'v': + FULLCHECK("vc"); + GLOBAL(); + tcp_mode = state; + break; + case 'z': /* zflag */ + FULLCHECK("zflag"); + query->have_zflag = state; + break; + global_option: + fprintf(stderr, "Ignored late global option: +%s\n", option); + break; + default: + invalid_option: + need_value: + fprintf(stderr, "Invalid option: +%s\n", option); + usage(); + } + return; +} + +/*% + * #ISC_TRUE returned if value was used + */ +static const char *single_dash_opts = "46hiv"; +/*static const char *dash_opts = "46bcfhiptvx";*/ +static isc_boolean_t +dash_option(const char *option, char *next, struct query *query, + isc_boolean_t global, isc_boolean_t *setname) +{ + char opt; + const char *value; + isc_result_t result; + isc_boolean_t value_from_next; + isc_consttextregion_t tr; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + char textname[MXNAME]; + struct in_addr in4; + struct in6_addr in6; + in_port_t srcport; + char *hash; + isc_uint32_t num; + + while (strpbrk(option, single_dash_opts) == &option[0]) { + /* + * Since the -[46hiv] options do not take an argument, + * account for them (in any number and/or combination) + * if they appear as the first character(s) of an opt. + */ + opt = option[0]; + switch (opt) { + case '4': + GLOBAL(); + if (have_ipv4) { + isc_net_disableipv6(); + have_ipv6 = ISC_FALSE; + } else { + fatal("can't find IPv4 networking"); + /* NOTREACHED */ + return (ISC_FALSE); + } + break; + case '6': + GLOBAL(); + if (have_ipv6) { + isc_net_disableipv4(); + have_ipv4 = ISC_FALSE; + } else { + fatal("can't find IPv6 networking"); + /* NOTREACHED */ + return (ISC_FALSE); + } + break; + case 'h': + help(); + exit(0); + break; + case 'i': + query->ip6_int = ISC_TRUE; + break; + case 'v': + exit(0); + break; + } + if (strlen(option) > 1U) + option = &option[1]; + else + return (ISC_FALSE); + } + opt = option[0]; + if (strlen(option) > 1U) { + value_from_next = ISC_FALSE; + value = &option[1]; + } else { + value_from_next = ISC_TRUE; + value = next; + } + if (value == NULL) + goto invalid_option; + switch (opt) { + case 'b': + GLOBAL(); + hash = strchr(value, '#'); + if (hash != NULL) { + result = parse_uint(&num, hash + 1, MAXPORT, + "port number"); + CHECK("parse_uint(srcport)", result); + srcport = num; + *hash = '\0'; + } else + srcport = 0; + if (have_ipv6 && inet_pton(AF_INET6, value, &in6) == 1) { + isc_sockaddr_fromin6(&srcaddr, &in6, srcport); + isc_net_disableipv4(); + } else if (have_ipv4 && inet_pton(AF_INET, value, &in4) == 1) { + isc_sockaddr_fromin(&srcaddr, &in4, srcport); + isc_net_disableipv6(); + } else { + if (hash != NULL) + *hash = '#'; + fatal("invalid address %s", value); + } + if (hash != NULL) + *hash = '#'; + have_src = ISC_TRUE; + return (value_from_next); + case 'c': + tr.base = value; + tr.length = strlen(value); + result = dns_rdataclass_fromtext(&rdclass, + (isc_textregion_t *)&tr); + CHECK("dns_rdataclass_fromtext", result); + query->rdclass = rdclass; + return (value_from_next); + case 'f': + batchname = value; + return (value_from_next); + case 'p': + GLOBAL(); + result = parse_uint(&num, value, MAXPORT, "port number"); + CHECK("parse_uint(port)", result); + port = num; + return (value_from_next); + case 't': + tr.base = value; + tr.length = strlen(value); + result = dns_rdatatype_fromtext(&rdtype, + (isc_textregion_t *)&tr); + CHECK("dns_rdatatype_fromtext", result); + query->rdtype = rdtype; + return (value_from_next); + case 'x': + get_reverse(textname, sizeof(textname), value, query->ip6_int); + strncpy(query->textname, textname, sizeof(query->textname)); + query->textname[sizeof(query->textname) - 1] = 0; + query->rdtype = dns_rdatatype_ptr; + query->rdclass = dns_rdataclass_in; + *setname = ISC_TRUE; + return (value_from_next); + global_option: + fprintf(stderr, "Ignored late global option: -%s\n", option); + usage(); + default: + invalid_option: + fprintf(stderr, "Invalid option: -%s\n", option); + usage(); + } + /* NOTREACHED */ + return (ISC_FALSE); +} + +static struct query * +clone_default_query() { + struct query *query; + + query = isc_mem_allocate(mctx, sizeof(struct query)); + if (query == NULL) + fatal("memory allocation failure in %s:%d", + __FILE__, __LINE__); + memmove(query, &default_query, sizeof(struct query)); + if (default_query.ecs_addr != NULL) { + size_t len = sizeof(isc_sockaddr_t); + + query->ecs_addr = isc_mem_allocate(mctx, len); + if (query->ecs_addr == NULL) + fatal("memory allocation failure in %s:%d", + __FILE__, __LINE__); + memmove(query->ecs_addr, default_query.ecs_addr, len); + } + + if (query->timeout == 0) + query->timeout = tcp_mode ? TCPTIMEOUT : UDPTIMEOUT; + + return query; +} + +static void +parse_args(isc_boolean_t is_batchfile, int argc, char **argv) +{ + struct query *query = NULL; + char batchline[MXNAME]; + int bargc; + char *bargv[64]; + int rc; + char **rv; + char *input; + isc_boolean_t global = ISC_TRUE; + + /* + * The semantics for parsing the args is a bit complex; if + * we don't have a host yet, make the arg apply globally, + * otherwise make it apply to the latest host. This is + * a bit different than the previous versions, but should + * form a consistent user interface. + * + * First, create a "default query" which won't actually be used + * anywhere, except for cloning into new queries + */ + + if (!is_batchfile) { + default_query.textname[0] = 0; + default_query.ip6_int = ISC_FALSE; + default_query.recurse = ISC_TRUE; + default_query.have_aaonly = ISC_FALSE; + default_query.have_adflag = ISC_TRUE; /*XXX*/ + default_query.have_cdflag = ISC_FALSE; + default_query.have_zflag = ISC_FALSE; + default_query.dnssec = ISC_FALSE; + default_query.expire = ISC_FALSE; + default_query.send_cookie = ISC_FALSE; + default_query.cookie = NULL; + default_query.nsid = ISC_FALSE; + default_query.rdtype = dns_rdatatype_a; + default_query.rdclass = dns_rdataclass_in; + default_query.udpsize = 0; + default_query.edns = 0; /*XXX*/ + default_query.ednsopts = NULL; + default_query.ednsoptscnt = 0; + default_query.ednsflags = 0; + default_query.ecs_addr = NULL; + default_query.timeout = 0; + default_query.udptimeout = 0; + default_query.udpretries = 3; + ISC_LINK_INIT(&default_query, link); + } + + if (is_batchfile) { + /* Processing '-f batchfile'. */ + query = clone_default_query(); + global = ISC_FALSE; + } else + query = &default_query; + + rc = argc; + rv = argv; + for (rc--, rv++; rc > 0; rc--, rv++) { + if (strncmp(rv[0], "%", 1) == 0) + break; + if (rv[0][0] == '@') { + if (server != NULL) + fatal("server already set to @%s", server); + server = &rv[0][1]; + } else if (rv[0][0] == '+') { + plus_option(&rv[0][1], query, global); + } else if (rv[0][0] == '-') { + isc_boolean_t setname = ISC_FALSE; + + if (rc <= 1) { + if (dash_option(&rv[0][1], NULL, query, + global, &setname)) { + rc--; + rv++; + } + } else { + if (dash_option(&rv[0][1], rv[1], query, + global, &setname)) { + rc--; + rv++; + } + } + if (setname) { + if (query == &default_query) + query = clone_default_query(); + ISC_LIST_APPEND(queries, query, link); + + default_query.textname[0] = 0; + query = clone_default_query(); + global = ISC_FALSE; + } + } else { + /* + * Anything which isn't an option + */ + if (query == &default_query) + query = clone_default_query(); + strncpy(query->textname, rv[0], + sizeof(query->textname)); + query->textname[sizeof(query->textname) - 1] = 0; + ISC_LIST_APPEND(queries, query, link); + + query = clone_default_query(); + global = ISC_FALSE; + /* XXX Error message */ + } + } + + /* + * If we have a batchfile, read the query list from it. + */ + if ((batchname != NULL) && !is_batchfile) { + if (strcmp(batchname, "-") == 0) + batchfp = stdin; + else + batchfp = fopen(batchname, "r"); + if (batchfp == NULL) { + perror(batchname); + fatal("couldn't open batch file '%s'", batchname); + } + while (fgets(batchline, sizeof(batchline), batchfp) != 0) { + bargc = 1; + if (batchline[0] == '\r' || batchline[0] == '\n' + || batchline[0] == '#' || batchline[0] == ';') + continue; + input = batchline; + bargv[bargc] = next_token(&input, " \t\r\n"); + while ((bargv[bargc] != NULL) && (bargc < 14)) { + bargc++; + bargv[bargc] = next_token(&input, " \t\r\n"); + } + + bargv[0] = argv[0]; + parse_args(ISC_TRUE, bargc, (char **)bargv); + } + if (batchfp != stdin) + fclose(batchfp); + } + if (query != &default_query) { + if (query->ecs_addr != NULL) + isc_mem_free(mctx, query->ecs_addr); + isc_mem_free(mctx, query); + } +} + +/*% Main processing routine for mdig */ +int +main(int argc, char *argv[]) { + struct query *query; + isc_sockaddr_t bind_any; + isc_log_t *lctx; + isc_logconfig_t *lcfg; + isc_entropy_t *ectx; + isc_taskmgr_t *taskmgr; + isc_task_t *task; + isc_timermgr_t *timermgr; + isc_socketmgr_t *socketmgr; + dns_dispatchmgr_t *dispatchmgr; + unsigned int attrs, attrmask; + dns_dispatch_t *dispatchvx; + dns_view_t *view; + int ns; + + RUNCHECK(isc_app_start()); + + dns_result_register(); + + if (isc_net_probeipv4() == ISC_R_SUCCESS) + have_ipv4 = ISC_TRUE; + if (isc_net_probeipv6() == ISC_R_SUCCESS) + have_ipv6 = ISC_TRUE; + if (!have_ipv4 && !have_ipv6) + fatal("could not find either IPv4 or IPv6"); + + mctx = NULL; +isc_mem_debugging = ISC_MEM_DEBUGRECORD; + RUNCHECK(isc_mem_create(0, 0, &mctx)); + + lctx = NULL; + lcfg = NULL; + RUNCHECK(isc_log_create(mctx, &lctx, &lcfg)); + + ectx = NULL; + RUNCHECK(isc_entropy_create(mctx, &ectx)); + RUNCHECK(isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE)); + RUNCHECK(isc_entropy_getdata(ectx, cookie_secret, + sizeof(cookie_secret), NULL, 0)); + + RUNCHECK(dst_lib_init(mctx, ectx, ISC_ENTROPY_GOODONLY)); + + ISC_LIST_INIT(queries); + parse_args(ISC_FALSE, argc, argv); + if (server == NULL) + fatal("a server '@xxx' is required"); + + ns = 0; + RUNCHECK(bind9_getaddresses(server, port, &dstaddr, 1, &ns)); + if (isc_sockaddr_pf(&dstaddr) == PF_INET && have_ipv6) { + isc_net_disableipv6(); + have_ipv6 = ISC_FALSE; + } else if (isc_sockaddr_pf(&dstaddr) == PF_INET6 && have_ipv4) { + isc_net_disableipv4(); + have_ipv4 = ISC_FALSE; + } + if (have_ipv4 && have_ipv6) + fatal("can't choose between IPv4 and IPv6"); + + taskmgr = NULL; + RUNCHECK(isc_taskmgr_create(mctx, 1, 0, &taskmgr)); + task = NULL; + RUNCHECK(isc_task_create(taskmgr, 0, &task)); + timermgr = NULL; + + RUNCHECK(isc_timermgr_create(mctx, &timermgr)); + socketmgr = NULL; + RUNCHECK(isc_socketmgr_create(mctx, &socketmgr)); + dispatchmgr = NULL; + RUNCHECK(dns_dispatchmgr_create(mctx, ectx, &dispatchmgr)); + + attrs = DNS_DISPATCHATTR_UDP | + DNS_DISPATCHATTR_MAKEQUERY; + if (have_ipv4) { + isc_sockaddr_any(&bind_any); + attrs |= DNS_DISPATCHATTR_IPV4; + } else { + isc_sockaddr_any6(&bind_any); + attrs |= DNS_DISPATCHATTR_IPV6; + } + attrmask = DNS_DISPATCHATTR_UDP | + DNS_DISPATCHATTR_TCP | + DNS_DISPATCHATTR_IPV4 | + DNS_DISPATCHATTR_IPV6; + dispatchvx = NULL; + RUNCHECK(dns_dispatch_getudp(dispatchmgr, socketmgr, taskmgr, + have_src ? &srcaddr : &bind_any, + 4096, 100, 100, 17, 19, + attrs, attrmask, &dispatchvx)); + requestmgr = NULL; + RUNCHECK(dns_requestmgr_create(mctx, timermgr, socketmgr, + taskmgr, dispatchmgr, + have_ipv4 ? dispatchvx : NULL, + have_ipv6 ? dispatchvx : NULL, + &requestmgr)); + + view = NULL; + RUNCHECK(dns_view_create(mctx, 0, "_test", &view)); + + query = ISC_LIST_HEAD(queries); + RUNCHECK(isc_app_onrun(mctx, task, sendqueries, query)); + + (void)isc_app_run(); + + query = ISC_LIST_HEAD(queries); + while (query != NULL) { + struct query *next = ISC_LIST_NEXT(query, link); + + if (query->ecs_addr != NULL) { + isc_mem_free(mctx, query->ecs_addr); + query->ecs_addr = NULL; + } + isc_mem_free(mctx, query); + query = next; + } + + if (default_query.ecs_addr != NULL) + isc_mem_free(mctx, default_query.ecs_addr); + + dns_view_detach(&view); + + dns_requestmgr_shutdown(requestmgr); + dns_requestmgr_detach(&requestmgr); + + dns_dispatch_detach(&dispatchvx); + dns_dispatchmgr_destroy(&dispatchmgr); + + isc_socketmgr_destroy(&socketmgr); + isc_timermgr_destroy(&timermgr); + + isc_task_shutdown(task); + isc_task_detach(&task); + isc_taskmgr_destroy(&taskmgr); + + dst_lib_destroy(); + isc_hash_destroy(); + isc_entropy_detach(&ectx); + + isc_log_destroy(&lctx); + + isc_mem_destroy(&mctx); + + isc_app_finish(); + + return (0); +} diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in index b4c6bc6c5b..7400bb3c3c 100644 --- a/bin/tests/system/conf.sh.in +++ b/bin/tests/system/conf.sh.in @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: conf.sh.in,v 1.71 2011/12/05 17:10:50 each Exp $ - # # Common configuration data for system tests, to be sourced into # other shell scripts. @@ -59,6 +57,7 @@ NSLOOKUP=$TOP/bin/dig/nslookup FEATURETEST=$TOP/bin/tests/system/feature-test RANDFILE=$TOP/bin/tests/system/random.data +MDIG=$TOP/bin/tests/mdig # The "stress" test is not run by default since it creates enough @@ -116,6 +115,7 @@ export JOURNALPRINT export KEYFRLAB export KEYGEN export LWRESD +export MDIG export NAMED export NSLOOKUP export NSUPDATE diff --git a/bin/tests/system/rrl/clean.sh b/bin/tests/system/rrl/clean.sh index 4a53b46f05..fb67784583 100644 --- a/bin/tests/system/rrl/clean.sh +++ b/bin/tests/system/rrl/clean.sh @@ -16,6 +16,6 @@ # Clean up after rrl tests. -rm -f dig.out* +rm -f dig.out* *mdig.out* rm -f */named.memstats */named.run */named.stats */log-* */session.key rm -f ns3/bl*.db */*.jnl */*.core */*.pid diff --git a/bin/tests/system/rrl/tests.sh b/bin/tests/system/rrl/tests.sh index 20c8b502de..49f71ea562 100644 --- a/bin/tests/system/rrl/tests.sh +++ b/bin/tests/system/rrl/tests.sh @@ -66,79 +66,81 @@ sec_start () { # turn off ${HOME}/.digrc HOME=/dev/null; export HOME -# $1=result name $2=domain name $3=dig options -digcmd () { - OFILE=$1; shift - DIG_DOM=$1; shift - ARGS="+nosearch +time=1 +tries=1 +ignore -p 5300 $* $DIG_DOM @$ns2" - #echo I:dig $ARGS 1>&2 - START=`date +%y%m%d%H%M.%S` - RESULT=`$DIG $ARGS 2>&1 | tee $OFILE=TEMP \ - | sed -n -e '/^;; AUTHORITY/,/^$/d' \ - -e '/^;; ADDITIONAL/,/^$/d' \ - -e 's/^[^;].* \([^ ]\{1,\}\)$/\1/p' \ - -e 's/;; flags.* tc .*/TC/p' \ - -e 's/;; .* status: NXDOMAIN.*/NXDOMAIN/p' \ - -e 's/;; .* status: SERVFAIL.*/SERVFAIL/p' \ - -e 's/;; connection timed out.*/drop/p' \ - -e 's/;; communications error to.*/drop/p' \ - | tr -d '\n'` - mv "$OFILE=TEMP" "$OFILE=$RESULT" - touch -t $START "$OFILE=$RESULT" -} - - # $1=number of tests $2=target domain $3=dig options QNUM=1 burst () { BURST_LIMIT=$1; shift BURST_DOM_BASE="$1"; shift + + XCNT=$CNT + CNT='XXX' + eval FILENAME="mdig.out-$BURST_DOM_BASE" + CNT=$XCNT + + DOMS="" CNTS=`$PERL -e 'for ( $i = 0; $i < '$BURST_LIMIT'; $i++) { printf "%03d\n", '$QNUM' + $i; }'` for CNT in $CNTS do - eval BURST_DOM="$BURST_DOM_BASE" - FILE="dig.out-$BURST_DOM-$CNT" - digcmd $FILE $BURST_DOM $* & + eval BURST_DOM="$BURST_DOM_BASE" + DOMS="$DOMS $BURST_DOM" done + ARGS="+nocookie +continue +time=1 +tries=1 -p 5300 $* @$ns2 $DOMS" + $MDIG $ARGS 2>&1 | tee -a full-$FILENAME | sed -n -e '/^;; AUTHORITY/,/^$/d' \ + -e '/^;; ADDITIONAL/,/^$/d' \ + -e 's/^[^;].* \([^ ]\{1,\}\)$/\1/p' \ + -e 's/;; flags.* tc .*/TC/p' \ + -e 's/;; .* status: NXDOMAIN.*/NXDOMAIN/p' \ + -e 's/;; .* status: NOERROR.*/NOERROR/p' \ + -e 's/;; .* status: SERVFAIL.*/SERVFAIL/p' \ + -e 's/response failed with timed out.*/drop/p' \ + -e 's/;; communications error to.*/drop/p' >> $FILENAME QNUM=`expr $QNUM + $BURST_LIMIT` } +# compare integers $1 and $2; ensure the difference is no more than $3 +range () { + $PERL -e 'if (abs(int($ARGV[0]) - int($ARGV[1])) > int($ARGV[2])) { exit(1) }' $1 $2 $3 +} # $1=domain $2=IP address $3=# of IP addresses $4=TC $5=drop # $6=NXDOMAIN $7=SERVFAIL or other errors ck_result() { - BAD= - wait - ADDRS=`ls dig.out-$1-*=$2 2>/dev/null | wc -l` + BAD=no + ADDRS=`egrep "^$2$" mdig.out-$1 2>/dev/null | wc -l` # count simple truncated and truncated NXDOMAIN as TC - TC=`ls dig.out-$1-*=TC dig.out-$1-*=NXDOMAINTC 2>/dev/null | wc -l` - DROP=`ls dig.out-$1-*=drop 2>/dev/null | wc -l` + TC=`egrep "^TC|NXDOMAINTC$" mdig.out-$1 2>/dev/null | wc -l` + DROP=`egrep "^drop$" mdig.out-$1 2>/dev/null | wc -l` # count NXDOMAIN and truncated NXDOMAIN as NXDOMAIN - NXDOMAIN=`ls dig.out-$1-*=NXDOMAIN dig.out-$1-*=NXDOMAINTC 2>/dev/null \ - | wc -l` - SERVFAIL=`ls dig.out-$1-*=SERVFAIL 2>/dev/null | wc -l` - if test $ADDRS -ne "$3"; then - setret "I:"$ADDRS" instead of $3 '$2' responses for $1" - BAD=yes - fi - if test $TC -ne "$4"; then - setret "I:"$TC" instead of $4 truncation responses for $1" - BAD=yes - fi - if test $DROP -ne "$5"; then - setret "I:"$DROP" instead of $5 dropped responses for $1" - BAD=yes - fi - if test $NXDOMAIN -ne "$6"; then - setret "I:"$NXDOMAIN" instead of $6 NXDOMAIN responses for $1" - BAD=yes - fi - if test $SERVFAIL -ne "$7"; then - setret "I:"$SERVFAIL" instead of $7 error responses for $1" - BAD=yes - fi + NXDOMAIN=`egrep "^NXDOMAIN|NXDOMAINTC$" mdig.out-$1 2>/dev/null | wc -l` + SERVFAIL=`egrep "^SERVFAIL$" mdig.out-$1 2>/dev/null | wc -l` + NOERROR=`egrep "^NOERROR$" mdig.out-$1 2>/dev/null | wc -l` + + range $ADDRS "$3" 1 || + setret "I:"$ADDRS" instead of $3 '$2' responses for $1" && + BAD=yes + + range $TC "$4" 1 || + setret "I:"$TC" instead of $4 truncation responses for $1" && + BAD=yes + + range $DROP "$5" 1 || + setret "I:"$DROP" instead of $5 dropped responses for $1" && + BAD=yes + + range $NXDOMAIN "$6" 1 || + setret "I:"$NXDOMAIN" instead of $6 NXDOMAIN responses for $1" && + BAD=yes + + range $SERVFAIL "$7" 1 || + setret "I:"$SERVFAIL" instead of $7 error responses for $1" && + BAD=yes + + range $NOERROR "$8" 1 || + setret "I:"$NOERROR" instead of $8 NOERROR responses for $1" && + BAD=yes + if test -z "$BAD"; then - rm -f dig.out-$1-* + rm -f mdig.out-$1 fi } @@ -164,7 +166,7 @@ sec_start burst 5 a1.tld3 +norec # basic rate limiting burst 3 a1.tld2 -# 1 second delay allows an additional response. +# delay allows an additional response. sleep 1 burst 10 a1.tld2 # Request 30 different qnames to try a wildcard. @@ -172,18 +174,18 @@ burst 30 'x$CNT.a2.tld2' # These should be counted and limited but are not. See RT33138. burst 10 'y.x$CNT.a2.tld2' -# IP TC drop NXDOMAIN SERVFAIL +# IP TC drop NXDOMAIN SERVFAIL NOERROR # referrals to "." -ck_result a1.tld3 '' 2 1 2 0 0 +ck_result a1.tld3 x 0 1 2 0 0 2 # check 13 results including 1 second delay that allows an additional response -ck_result a1.tld2 192.0.2.1 3 4 6 0 0 +ck_result a1.tld2 192.0.2.1 3 4 6 0 0 8 # Check the wild card answers. # The parent name of the 30 requests is counted. -ck_result 'x*.a2.tld2' 192.0.2.2 2 10 18 0 0 +ck_result 'x*.a2.tld2' 192.0.2.2 2 10 18 0 0 12 # These should be limited but are not. See RT33138. -ck_result 'y.x*.a2.tld2' 192.0.2.2 10 0 0 0 0 +ck_result 'y.x*.a2.tld2' 192.0.2.2 10 0 0 0 0 10 ######### sec_start @@ -193,15 +195,15 @@ burst 10 'y$CNT.a3.tld3' burst 10 'z$CNT.a4.tld2' # 10 identical recursive responses are limited -ck_result 'x.a3.tld3' 192.0.3.3 2 3 5 0 0 +ck_result 'x.a3.tld3' 192.0.3.3 2 3 5 0 0 5 # 10 different recursive responses are not limited -ck_result 'y*.a3.tld3' 192.0.3.3 10 0 0 0 0 +ck_result 'y*.a3.tld3' 192.0.3.3 10 0 0 0 0 10 # 10 different NXDOMAIN responses are limited based on the parent name. # We count 13 responses because we count truncated NXDOMAIN responses # as both truncated and NXDOMAIN. -ck_result 'z*.a4.tld2' x 0 3 5 5 0 +ck_result 'z*.a4.tld2' x 0 3 5 5 0 0 $RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats ckstats first dropped 36 @@ -214,22 +216,22 @@ sec_start burst 10 a5.tld2 +tcp burst 10 a6.tld2 -b $ns7 burst 10 a7.tld4 -burst 2 a8.tld2 AAAA -burst 2 a8.tld2 TXT -burst 2 a8.tld2 SPF +burst 2 a8.tld2 -t AAAA +burst 2 a8.tld2 -t TXT +burst 2 a8.tld2 -t SPF -# IP TC drop NXDOMAIN SERVFAIL +# IP TC drop NXDOMAIN SERVFAIL NOERROR # TCP responses are not rate limited -ck_result a5.tld2 192.0.2.5 10 0 0 0 0 +ck_result a5.tld2 192.0.2.5 10 0 0 0 0 10 # whitelisted client is not rate limited -ck_result a6.tld2 192.0.2.6 10 0 0 0 0 +ck_result a6.tld2 192.0.2.6 10 0 0 0 0 10 # Errors such as SERVFAIL are rate limited. -ck_result a7.tld4 x 0 0 8 0 2 +ck_result a7.tld4 x 0 0 8 0 2 0 # NODATA responses are counted as the same regardless of qtype. -ck_result a8.tld2 '' 2 2 2 0 0 +ck_result a8.tld2 x 0 2 2 0 0 4 $RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats ckstats second dropped 46 @@ -239,13 +241,13 @@ ckstats second truncated 23 ######### sec_start -# IP TC drop NXDOMAIN SERVFAIL +# IP TC drop NXDOMAIN SERVFAIL NOERROR # all-per-second # The qnames are all unique but the client IP address is constant. QNUM=101 burst 60 'all$CNT.a9.tld2' -ck_result 'a*.a9.tld2' 192.0.2.8 50 0 10 0 0 +ck_result 'a*.a9.tld2' 192.0.2.8 50 0 10 0 0 50 $RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats ckstats final dropped 56