From 33a741f8973938e4b09f094f7a281d2740e60ae9 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Tue, 31 Jan 2023 13:30:12 -0800 Subject: [PATCH 01/14] add dns_view_addtrustedkey() the new dns_view_addtrustedkey() function allows a view's trust anchors to be updated directly. this code was formerly in dns_client_addtrustedkey(), which is now a wrapper around dns_view_addtrustedkey(). --- lib/dns/client.c | 39 +-------------------------------- lib/dns/include/dns/client.h | 9 ++++---- lib/dns/include/dns/view.h | 25 +++++++++++++++++++++ lib/dns/view.c | 42 ++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 42 deletions(-) diff --git a/lib/dns/client.c b/lib/dns/client.c index a32f33645b..579b9c3aab 100644 --- a/lib/dns/client.c +++ b/lib/dns/client.c @@ -1074,45 +1074,8 @@ isc_result_t dns_client_addtrustedkey(dns_client_t *client, dns_rdataclass_t rdclass, dns_rdatatype_t rdtype, const dns_name_t *keyname, isc_buffer_t *databuf) { - isc_result_t result; - dns_keytable_t *secroots = NULL; - dns_name_t *name = NULL; - char rdatabuf[DST_KEY_MAXSIZE]; - unsigned char digest[ISC_MAX_MD_SIZE]; - dns_rdata_ds_t ds; - dns_rdata_t rdata; - isc_buffer_t b; - REQUIRE(DNS_CLIENT_VALID(client)); REQUIRE(rdclass == dns_rdataclass_in); - CHECK(dns_view_getsecroots(client->view, &secroots)); - - DE_CONST(keyname, name); - - if (rdtype != dns_rdatatype_dnskey && rdtype != dns_rdatatype_ds) { - result = ISC_R_NOTIMPLEMENTED; - goto cleanup; - } - - isc_buffer_init(&b, rdatabuf, sizeof(rdatabuf)); - dns_rdata_init(&rdata); - isc_buffer_setactive(databuf, isc_buffer_usedlength(databuf)); - CHECK(dns_rdata_fromwire(&rdata, rdclass, rdtype, databuf, - DNS_DECOMPRESS_NEVER, &b)); - - if (rdtype == dns_rdatatype_ds) { - CHECK(dns_rdata_tostruct(&rdata, &ds, NULL)); - } else { - CHECK(dns_ds_fromkeyrdata(name, &rdata, DNS_DSDIGEST_SHA256, - digest, &ds)); - } - - CHECK(dns_keytable_add(secroots, false, false, name, &ds, NULL, NULL)); - -cleanup: - if (secroots != NULL) { - dns_keytable_detach(&secroots); - } - return (result); + return (dns_view_addtrustedkey(client->view, rdtype, keyname, databuf)); } diff --git a/lib/dns/include/dns/client.h b/lib/dns/include/dns/client.h index 166822433b..543a5ffb5d 100644 --- a/lib/dns/include/dns/client.h +++ b/lib/dns/include/dns/client.h @@ -279,10 +279,11 @@ dns_client_addtrustedkey(dns_client_t *client, dns_rdataclass_t rdclass, dns_rdatatype_t rdtype, const dns_name_t *keyname, isc_buffer_t *keydatabuf); /*%< - * Add a DNSSEC trusted key for the 'rdclass' class. A view for the 'rdclass' - * class must be created beforehand. 'rdtype' is the type of the RR data - * for the key, either DNSKEY or DS. 'keyname' is the DNS name of the key, - * and 'keydatabuf' stores the RR data. + * Add a DNSSEC trusted key for the 'rdclass' class (only class 'IN' is + * currently supported). A view for the 'rdclass' class must be created + * beforehand. 'rdtype' is the type of the RR data for the key, either + * DNSKEY or DS. 'keyname' is the DNS name of the key, and 'keydatabuf' + * stores the RR data. * * Requires: * diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index 0a697fc8c0..71f7541019 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -1262,4 +1262,29 @@ dns_view_getdispatchmgr(dns_view_t *view); * by the resolver and request managers to send and receive DNS * messages. */ + +isc_result_t +dns_view_addtrustedkey(dns_view_t *view, dns_rdatatype_t rdtype, + const dns_name_t *keyname, isc_buffer_t *databuf); +/*%< + * Add a DNSSEC trusted key to a view of class 'IN'. 'rdtype' is the type + * of the RR data for the key, either DNSKEY or DS. 'keyname' is the DNS + * name of the key, and 'databuf' stores the RR data. + + * Requires: + * + *\li 'view' is a valid view. + + *\li 'view' is class 'IN'. + * + *\li 'keyname' is a valid name. + * + *\li 'keydatabuf' is a valid buffer. + * + * Returns: + * + *\li #ISC_R_SUCCESS On success. + * + *\li Anything else Failure. + */ ISC_LANG_ENDDECLS diff --git a/lib/dns/view.c b/lib/dns/view.c index 551b1ebd4c..686075b309 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -2323,3 +2324,44 @@ dns_view_getdispatchmgr(dns_view_t *view) { REQUIRE(DNS_VIEW_VALID(view)); return (view->dispatchmgr); } + +isc_result_t +dns_view_addtrustedkey(dns_view_t *view, dns_rdatatype_t rdtype, + const dns_name_t *keyname, isc_buffer_t *databuf) { + isc_result_t result; + dns_name_t *name = NULL; + char rdatabuf[DST_KEY_MAXSIZE]; + unsigned char digest[ISC_MAX_MD_SIZE]; + dns_rdata_ds_t ds; + dns_rdata_t rdata; + isc_buffer_t b; + + REQUIRE(DNS_VIEW_VALID(view)); + REQUIRE(view->rdclass == dns_rdataclass_in); + + DE_CONST(keyname, name); + + if (rdtype != dns_rdatatype_dnskey && rdtype != dns_rdatatype_ds) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + + isc_buffer_init(&b, rdatabuf, sizeof(rdatabuf)); + dns_rdata_init(&rdata); + isc_buffer_setactive(databuf, isc_buffer_usedlength(databuf)); + CHECK(dns_rdata_fromwire(&rdata, view->rdclass, rdtype, databuf, + DNS_DECOMPRESS_NEVER, &b)); + + if (rdtype == dns_rdatatype_ds) { + CHECK(dns_rdata_tostruct(&rdata, &ds, NULL)); + } else { + CHECK(dns_ds_fromkeyrdata(name, &rdata, DNS_DSDIGEST_SHA256, + digest, &ds)); + } + + CHECK(dns_keytable_add(view->secroots_priv, false, false, name, &ds, + NULL, NULL)); + +cleanup: + return (result); +} From 4ad95e05676d104e9092323f33570060afb06726 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Tue, 31 Jan 2023 13:30:12 -0800 Subject: [PATCH 02/14] add ns_interface_create() add a public function ns_interface_create() allowing the caller to set up a listening interface directly without having to set up listen-on and scan network interfaces. --- lib/isc/netmgr/netmgr-int.h | 1 - lib/isc/netmgr/tcp.c | 3 +-- lib/isc/sockaddr.c | 4 ++-- lib/ns/include/ns/interfacemgr.h | 8 ++++++++ lib/ns/interfacemgr.c | 16 ++++++++++------ tests/ns/query_test.c | 16 ++++++---------- 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h index 96a6aab991..095baf09b0 100644 --- a/lib/isc/netmgr/netmgr-int.h +++ b/lib/isc/netmgr/netmgr-int.h @@ -479,7 +479,6 @@ struct isc_nmsocket { isc_nmsocket_type type; isc__networker_t *worker; - isc_mutex_t lock; isc_barrier_t listen_barrier; isc_barrier_t stop_barrier; diff --git a/lib/isc/netmgr/tcp.c b/lib/isc/netmgr/tcp.c index 66c3a0f9e4..948f7c1722 100644 --- a/lib/isc/netmgr/tcp.c +++ b/lib/isc/netmgr/tcp.c @@ -350,8 +350,7 @@ start_tcp_child_job(void *arg) { REQUIRE(sock->tid == isc_tid()); sa_family_t sa_family = sock->iface.type.sa.sa_family; - int r; - int flags = 0; + int r, flags = 0; isc_result_t result = ISC_R_UNSET; isc_loop_t *loop = sock->worker->loop; diff --git a/lib/isc/sockaddr.c b/lib/isc/sockaddr.c index 7c99c75c67..512f08bdee 100644 --- a/lib/isc/sockaddr.c +++ b/lib/isc/sockaddr.c @@ -491,9 +491,9 @@ isc_sockaddr_fromsockaddr(isc_sockaddr_t *isa, const struct sockaddr *sa) { return (ISC_R_NOTIMPLEMENTED); } - memset(isa, 0, sizeof(isc_sockaddr_t)); + *isa = (isc_sockaddr_t){ .length = length, + .link = ISC_LINK_INITIALIZER }; memmove(isa, sa, length); - isa->length = length; return (ISC_R_SUCCESS); } diff --git a/lib/ns/include/ns/interfacemgr.h b/lib/ns/include/ns/interfacemgr.h index 3da1c93d85..651f4d429b 100644 --- a/lib/ns/include/ns/interfacemgr.h +++ b/lib/ns/include/ns/interfacemgr.h @@ -193,3 +193,11 @@ ns_interfacemgr_dynamic_updates_are_reliable(void); * disabled. That is the case on the platforms where kernel-based * mechanisms for tracking networking interface states is reliable enough. */ + +void +ns_interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, + const char *name, ns_interface_t **ifpret); +/*%< + * Create an interface 'name' associated with address 'addr'. If + * 'name' is NULL then it is set to "default". + */ diff --git a/lib/ns/interfacemgr.c b/lib/ns/interfacemgr.c index bb51603960..c21003c2c2 100644 --- a/lib/ns/interfacemgr.c +++ b/lib/ns/interfacemgr.c @@ -443,16 +443,20 @@ ns_interfacemgr_shutdown(ns_interfacemgr_t *mgr) { } } -static void -interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, const char *name, - ns_interface_t **ifpret) { +void +ns_interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, + const char *name, ns_interface_t **ifpret) { ns_interface_t *ifp = NULL; + const char *default_name = "default"; REQUIRE(NS_INTERFACEMGR_VALID(mgr)); ifp = isc_mem_get(mgr->mctx, sizeof(*ifp)); *ifp = (ns_interface_t){ .generation = mgr->generation, .addr = *addr }; + if (name == NULL) { + name = default_name; + } strlcpy(ifp->name, name, sizeof(ifp->name)); isc_mutex_init(&ifp->lock); @@ -651,7 +655,7 @@ interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, const char *name, ifp = *ifpret; if (ifp == NULL) { - interface_create(mgr, addr, name, &ifp); + ns_interface_create(mgr, addr, name, &ifp); } else { REQUIRE(!LISTENING(ifp)); } @@ -1193,8 +1197,8 @@ do_scan(ns_interfacemgr_t *mgr, bool verbose, bool config) { mgr->aclenv, &match, NULL); if (match <= 0) { ns_interface_t *new = NULL; - interface_create(mgr, &listen_sockaddr, - interface.name, &new); + ns_interface_create(mgr, &listen_sockaddr, + interface.name, &new); continue; } diff --git a/tests/ns/query_test.c b/tests/ns/query_test.c index d0466cb083..2c538c4320 100644 --- a/tests/ns/query_test.c +++ b/tests/ns/query_test.c @@ -247,19 +247,15 @@ typedef struct { dns_rdatatype_t qtype; /* QTYPE */ unsigned int qflags; /* query flags */ bool disable_name_checks; /* if set to true, owner - * name - * checks will + * name checks will * be disabled for the - * view created - * */ + * view created + */ bool recursive_service; /* if set to true, the view - * created will - * have a cache - * database - * attached */ + * created will have a cache + * database attached */ const char *auth_zone_origin; /* origin name of the zone - * the - * created view will be + * the created view will be * authoritative for */ const char *auth_zone_path; /* path to load the * authoritative From fe7ed2ba24a88f8ef13d6689cd2d35c02d0e3d81 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Tue, 31 Jan 2023 13:30:12 -0800 Subject: [PATCH 03/14] update stream sockets with bound address/port when isc_nm_listenstreamdns() is called with a local port of 0, a random port is chosen. call uv_getsockname() to determine what the port is as soon as the socket is bound, and add a function isc_nmsocket_getaddr() to retrieve it, so that the caller can connect to the listening socket. this will be used in cases where the same process is acting as both client and server. --- lib/isc/include/isc/netmgr.h | 6 ++++++ lib/isc/netmgr/netmgr.c | 6 ++++++ lib/isc/netmgr/streamdns.c | 5 +++++ lib/isc/netmgr/tcp.c | 18 ++++++++++++++++++ lib/isc/netmgr/tlsstream.c | 5 +++++ 5 files changed, 40 insertions(+) diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h index 5c7a4e735a..aa90e6f80a 100644 --- a/lib/isc/include/isc/netmgr.h +++ b/lib/isc/include/isc/netmgr.h @@ -729,3 +729,9 @@ isc_nmhandle_set_tcp_nodelay(isc_nmhandle_t *handle, const bool value); * * \li 'handle' is a valid netmgr handle object. */ + +isc_sockaddr_t +isc_nmsocket_getaddr(isc_nmsocket_t *sock); +/*%< + * Return the local address of 'sock'. + */ diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c index 42b8f834c6..bdc5905aeb 100644 --- a/lib/isc/netmgr/netmgr.c +++ b/lib/isc/netmgr/netmgr.c @@ -2538,6 +2538,12 @@ isc_nmhandle_set_tcp_nodelay(isc_nmhandle_t *handle, const bool value) { return (result); } +isc_sockaddr_t +isc_nmsocket_getaddr(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + return (sock->iface); +} + #if ISC_NETMGR_TRACE /* * Dump all active sockets in netmgr. We output to stderr diff --git a/lib/isc/netmgr/streamdns.c b/lib/isc/netmgr/streamdns.c index 4e3cf5b5dc..2b3c8c38b9 100644 --- a/lib/isc/netmgr/streamdns.c +++ b/lib/isc/netmgr/streamdns.c @@ -748,6 +748,11 @@ isc_nm_listenstreamdns(isc_nm_t *mgr, uint32_t workers, isc_sockaddr_t *iface, return (result); } + /* copy the actual port we're listening on into sock->iface */ + if (isc_sockaddr_getport(iface) == 0) { + listener->iface = listener->outer->iface; + } + listener->result = result; atomic_store(&listener->active, true); atomic_store(&listener->listening, true); diff --git a/lib/isc/netmgr/tcp.c b/lib/isc/netmgr/tcp.c index 948f7c1722..5de84444ad 100644 --- a/lib/isc/netmgr/tcp.c +++ b/lib/isc/netmgr/tcp.c @@ -353,6 +353,7 @@ start_tcp_child_job(void *arg) { int r, flags = 0; isc_result_t result = ISC_R_UNSET; isc_loop_t *loop = sock->worker->loop; + struct sockaddr_storage ss; (void)isc__nm_socket_min_mtu(sock->fd, sa_family); (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG); @@ -417,8 +418,25 @@ start_tcp_child_job(void *arg) { atomic_store(&sock->listening, true); + if (sock->tid == 0) { + r = uv_tcp_getsockname(&sock->uv_handle.tcp, + (struct sockaddr *)&ss, + &(int){ sizeof(ss) }); + if (r != 0) { + goto done; + } + + result = isc_sockaddr_fromsockaddr(&sock->parent->iface, + (struct sockaddr *)&ss); + if (result != ISC_R_SUCCESS) { + goto done_result; + } + } + done: result = isc_uverr2result(r); + +done_result: atomic_fetch_add(&sock->parent->rchildren, 1); if (result != ISC_R_SUCCESS) { diff --git a/lib/isc/netmgr/tlsstream.c b/lib/isc/netmgr/tlsstream.c index 3f74d9608b..6d6cc12df0 100644 --- a/lib/isc/netmgr/tlsstream.c +++ b/lib/isc/netmgr/tlsstream.c @@ -937,6 +937,11 @@ isc_nm_listentls(isc_nm_t *mgr, uint32_t workers, isc_sockaddr_t *iface, return (result); } + /* copy the actual port we're listening on into sock->iface */ + if (isc_sockaddr_getport(iface) == 0) { + tlssock->iface = tlssock->outer->iface; + } + /* wait for listen result */ isc__nmsocket_attach(tlssock->outer, &tsock); tlssock->result = result; From d91097e0c7154b82a54cae7e181bfb25925ecb48 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Tue, 11 Jan 2022 23:35:22 -0800 Subject: [PATCH 04/14] change ns__client_request() to ns_client_request() in the future we'll want to call this function from outside named, so change the name to one suitable for external access. --- lib/ns/client.c | 4 ++-- lib/ns/include/ns/client.h | 4 ++-- lib/ns/interfacemgr.c | 8 ++++---- tests/libtest/ns.c | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/ns/client.c b/lib/ns/client.c index 13b4b4f49b..c016c8986c 100644 --- a/lib/ns/client.c +++ b/lib/ns/client.c @@ -1680,8 +1680,8 @@ ns__client_put_cb(void *client0) { * or tcpmsg (TCP case). */ void -ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult, - isc_region_t *region, void *arg) { +ns_client_request(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *arg) { ns_client_t *client = NULL; isc_result_t result; isc_result_t sigresult = ISC_R_SUCCESS; diff --git a/lib/ns/include/ns/client.h b/lib/ns/include/ns/client.h index bfd4e131c4..28f4bfd929 100644 --- a/lib/ns/include/ns/client.h +++ b/lib/ns/include/ns/client.h @@ -445,8 +445,8 @@ ns_client_addopt(ns_client_t *client, dns_message_t *message, */ void -ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult, - isc_region_t *region, void *arg); +ns_client_request(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *arg); /*%< * Handle client requests. diff --git a/lib/ns/interfacemgr.c b/lib/ns/interfacemgr.c index c21003c2c2..ae73e42340 100644 --- a/lib/ns/interfacemgr.c +++ b/lib/ns/interfacemgr.c @@ -482,7 +482,7 @@ ns_interface_listenudp(ns_interface_t *ifp) { /* Reserve space for an ns_client_t with the netmgr handle */ result = isc_nm_listenudp(ifp->mgr->nm, ISC_NM_LISTEN_ALL, &ifp->addr, - ns__client_request, ifp, + ns_client_request, ifp, &ifp->udplistensocket); return (result); } @@ -492,7 +492,7 @@ ns_interface_listentcp(ns_interface_t *ifp) { isc_result_t result; result = isc_nm_listenstreamdns( - ifp->mgr->nm, ISC_NM_LISTEN_ALL, &ifp->addr, ns__client_request, + ifp->mgr->nm, ISC_NM_LISTEN_ALL, &ifp->addr, ns_client_request, ifp, ns__client_tcpconn, ifp, ifp->mgr->backlog, &ifp->mgr->sctx->tcpquota, NULL, &ifp->tcplistensocket); if (result != ISC_R_SUCCESS) { @@ -525,7 +525,7 @@ ns_interface_listentls(ns_interface_t *ifp, isc_tlsctx_t *sslctx) { isc_result_t result; result = isc_nm_listenstreamdns( - ifp->mgr->nm, ISC_NM_LISTEN_ALL, &ifp->addr, ns__client_request, + ifp->mgr->nm, ISC_NM_LISTEN_ALL, &ifp->addr, ns_client_request, ifp, ns__client_tcpconn, ifp, ifp->mgr->backlog, &ifp->mgr->sctx->tcpquota, sslctx, &ifp->tcplistensocket); @@ -559,7 +559,7 @@ load_http_endpoints(isc_nm_http_endpoints_t *epset, ns_interface_t *ifp, for (size_t i = 0; i < neps; i++) { result = isc_nm_http_endpoints_add(epset, eps[i], - ns__client_request, ifp); + ns_client_request, ifp); if (result != ISC_R_SUCCESS) { break; } diff --git a/tests/libtest/ns.c b/tests/libtest/ns.c index 49c60256e2..4e450c86dd 100644 --- a/tests/libtest/ns.c +++ b/tests/libtest/ns.c @@ -456,7 +456,7 @@ ns_test_qctx_create(const ns_test_qctx_create_params_t *params, /* * Allow recursion for the client. As NS_CLIENTATTR_RA normally gets - * set in ns__client_request(), i.e. earlier than the unit tests hook + * set in ns_client_request(), i.e. earlier than the unit tests hook * into the call chain, just set it manually. */ client->attributes |= NS_CLIENTATTR_RA; From d541ddba663e0a862ca9f791ff33794026188eb5 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Tue, 31 Jan 2023 13:30:12 -0800 Subject: [PATCH 05/14] add "delv +ns" command, for full internal resolution "delv +ns" (name server mode) instantiates a full recursive resolver inside delv and uses it to resolve the requested name and type, logging every authoritative response received to iterative queries in the process. this is intended to replace "dig +trace"; it much more accurately duplicates the behavior of named when resolving a query with a cold cache. --- bin/delv/Makefile.am | 2 + bin/delv/delv.c | 442 +++++++++++++++++++++++++++++++++++-------- bin/delv/delv.rst | 14 ++ 3 files changed, 382 insertions(+), 76 deletions(-) diff --git a/bin/delv/Makefile.am b/bin/delv/Makefile.am index df4661f6e9..032da0e545 100644 --- a/bin/delv/Makefile.am +++ b/bin/delv/Makefile.am @@ -4,6 +4,7 @@ AM_CPPFLAGS += \ -I$(top_builddir)/include \ $(LIBISC_CFLAGS) \ $(LIBDNS_CFLAGS) \ + $(LIBNS_CFLAGS) \ $(LIBISCCFG_CFLAGS) AM_CPPFLAGS += \ @@ -17,4 +18,5 @@ delv_SOURCES = \ delv_LDADD = \ $(LIBISC_LIBS) \ $(LIBDNS_LIBS) \ + $(LIBNS_LIBS) \ $(LIBISCCFG_LIBS) diff --git a/bin/delv/delv.c b/bin/delv/delv.c index 7fa6eb8679..617bc0f1d4 100644 --- a/bin/delv/delv.c +++ b/bin/delv/delv.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -42,20 +43,28 @@ #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 @@ -63,6 +72,10 @@ #include #include +#include +#include +#include + #include #define CHECK(r) \ @@ -78,10 +91,20 @@ char *progname; static isc_mem_t *mctx = NULL; static isc_log_t *lctx = NULL; +static dns_view_t *view = NULL; +static ns_server_t *sctx = NULL; +static ns_interface_t *ifp = NULL; +static dns_dispatch_t *dispatch = NULL; +static dns_db_t *roothints = NULL; +static isc_stats_t *resstats = NULL; +static dns_stats_t *resquerystats = NULL; /* Managers */ static isc_nm_t *netmgr = NULL; static isc_loopmgr_t *loopmgr = NULL; +static dns_dispatchmgr_t *dispatchmgr = NULL; +static dns_requestmgr_t *requestmgr = NULL; +static ns_interfacemgr_t *interfacemgr = NULL; /* TLS */ static isc_tlsctx_cache_t *tlsctx_client_cache = NULL; @@ -89,6 +112,7 @@ static isc_tlsctx_cache_t *tlsctx_client_cache = NULL; /* Configurables */ static char *server = NULL; static const char *port = "53"; +static uint32_t destport = 53; static isc_sockaddr_t *srcaddr4 = NULL, *srcaddr6 = NULL; static isc_sockaddr_t a4, a6; static char *curqname = NULL, *qname = NULL; @@ -101,7 +125,7 @@ static uint32_t splitwidth = 0xffffffff; static bool showcomments = true, showdnssec = true, showtrust = true, rrcomments = true, noclass = false, nocrypto = false, nottl = false, multiline = false, short_form = false, print_unknown_format = false, - yaml = false; + yaml = false, fulltrace = false; static bool resolve_trace = false, validator_trace = false, message_trace = false; @@ -189,6 +213,8 @@ usage(void) { "records)\n" " +[no]mtrace (Trace messages " "received)\n" + " +[no]ns (Run internal name " + "server)\n" " +[no]multiline (Print records in an " "expanded format)\n" " +[no]root (DNSSEC validation trust " @@ -424,7 +450,7 @@ print_status(dns_rdataset_t *rdataset) { } } -static isc_result_t +static void printdata(dns_rdataset_t *rdataset, dns_name_t *owner) { isc_result_t result = ISC_R_SUCCESS; static dns_trust_t trust; @@ -437,12 +463,13 @@ printdata(dns_rdataset_t *rdataset, dns_name_t *owner) { if (!dns_rdataset_isassociated(rdataset)) { char namebuf[DNS_NAME_FORMATSIZE]; dns_name_format(owner, namebuf, sizeof(namebuf)); - delv_log(ISC_LOG_DEBUG(4), "WARN: empty rdataset %s", namebuf); - return (ISC_R_SUCCESS); + delv_log(ISC_LOG_DEBUG(4), "warning: empty rdataset %s", + namebuf); + return; } if (!showdnssec && rdataset->type == dns_rdatatype_rrsig) { - return (ISC_R_SUCCESS); + return; } if (first || rdataset->trust != trust) { @@ -516,8 +543,6 @@ cleanup: if (t != NULL) { isc_mem_put(mctx, t, len); } - - return (ISC_R_SUCCESS); } static isc_result_t @@ -570,7 +595,7 @@ static isc_result_t convert_name(dns_fixedname_t *fn, dns_name_t **name, const char *text) { isc_result_t result; isc_buffer_t b; - dns_name_t *n; + dns_name_t *n = NULL; unsigned int len; REQUIRE(fn != NULL && name != NULL && text != NULL); @@ -582,7 +607,7 @@ convert_name(dns_fixedname_t *fn, dns_name_t **name, const char *text) { result = dns_name_fromtext(n, &b, dns_rootname, 0, NULL); if (result != ISC_R_SUCCESS) { - delv_log(ISC_LOG_ERROR, "failed to convert QNAME %s: %s", text, + delv_log(ISC_LOG_ERROR, "failed to convert name %s: %s", text, isc_result_totext(result)); return (result); } @@ -592,7 +617,7 @@ convert_name(dns_fixedname_t *fn, dns_name_t **name, const char *text) { } static isc_result_t -key_fromconfig(const cfg_obj_t *key, dns_client_t *client) { +key_fromconfig(const cfg_obj_t *key, dns_client_t *client, dns_view_t *toview) { dns_rdata_dnskey_t dnskey; dns_rdata_ds_t ds; uint32_t rdata1, rdata2, rdata3; @@ -615,6 +640,8 @@ key_fromconfig(const cfg_obj_t *key, dns_client_t *client) { } anchortype; const cfg_obj_t *obj; + REQUIRE(client != NULL || toview != NULL); + keynamestr = cfg_obj_asstring(cfg_tuple_get(key, "name")); CHECK(convert_name(&fkeyname, &keyname, keynamestr)); @@ -708,9 +735,15 @@ key_fromconfig(const cfg_obj_t *key, dns_client_t *client) { CHECK(dns_rdata_fromstruct(NULL, dnskey.common.rdclass, dnskey.common.rdtype, &dnskey, &rrdatabuf)); - CHECK(dns_client_addtrustedkey(client, dns_rdataclass_in, - dns_rdatatype_dnskey, keyname, - &rrdatabuf)); + if (client != NULL) { + CHECK(dns_client_addtrustedkey( + client, dns_rdataclass_in, dns_rdatatype_dnskey, + keyname, &rrdatabuf)); + } else if (toview != NULL) { + CHECK(dns_view_addtrustedkey(toview, + dns_rdatatype_dnskey, + keyname, &rrdatabuf)); + } break; case INITIAL_DS: case STATIC_DS: @@ -751,9 +784,14 @@ key_fromconfig(const cfg_obj_t *key, dns_client_t *client) { CHECK(dns_rdata_fromstruct(NULL, ds.common.rdclass, ds.common.rdtype, &ds, &rrdatabuf)); - CHECK(dns_client_addtrustedkey(client, dns_rdataclass_in, - dns_rdatatype_ds, keyname, - &rrdatabuf)); + if (client != NULL) { + CHECK(dns_client_addtrustedkey( + client, dns_rdataclass_in, dns_rdatatype_ds, + keyname, &rrdatabuf)); + } else if (toview != NULL) { + CHECK(dns_view_addtrustedkey(toview, dns_rdatatype_ds, + keyname, &rrdatabuf)); + } } num_keys++; @@ -777,7 +815,7 @@ cleanup: } static isc_result_t -load_keys(const cfg_obj_t *keys, dns_client_t *client) { +load_keys(const cfg_obj_t *keys, dns_client_t *client, dns_view_t *toview) { const cfg_listelt_t *elt, *elt2; const cfg_obj_t *key, *keylist; isc_result_t result = ISC_R_SUCCESS; @@ -790,7 +828,7 @@ load_keys(const cfg_obj_t *keys, dns_client_t *client) { elt2 = cfg_list_next(elt2)) { key = cfg_listelt_value(elt2); - CHECK(key_fromconfig(key, client)); + CHECK(key_fromconfig(key, client, toview)); } } @@ -802,7 +840,7 @@ cleanup: } static isc_result_t -setup_dnsseckeys(dns_client_t *client) { +setup_dnsseckeys(dns_client_t *client, dns_view_t *toview) { isc_result_t result; cfg_parser_t *parser = NULL; const cfg_obj_t *trusted_keys = NULL; @@ -853,13 +891,13 @@ setup_dnsseckeys(dns_client_t *client) { cfg_map_get(bindkeys, "trust-anchors", &trust_anchors); if (trusted_keys != NULL) { - CHECK(load_keys(trusted_keys, client)); + CHECK(load_keys(trusted_keys, client, toview)); } if (managed_keys != NULL) { - CHECK(load_keys(managed_keys, client)); + CHECK(load_keys(managed_keys, client, toview)); } if (trust_anchors != NULL) { - CHECK(load_keys(trust_anchors, client)); + CHECK(load_keys(trust_anchors, client, toview)); } result = ISC_R_SUCCESS; @@ -883,21 +921,15 @@ cleanup: static isc_result_t addserver(dns_client_t *client) { - struct addrinfo hints, *res, *cur; + struct addrinfo hints, *res = NULL, *cur = NULL; int gaierror; struct in_addr in4; struct in6_addr in6; - isc_sockaddr_t *sa; + isc_sockaddr_t *sa = NULL; isc_sockaddrlist_t servers; - uint32_t destport; isc_result_t result; dns_name_t *name = NULL; - result = parse_uint(&destport, port, 0xffff, "port"); - if (result != ISC_R_SUCCESS) { - fatal("Couldn't parse port number"); - } - ISC_LIST_INIT(servers); if (inet_pton(AF_INET, server, &in4) == 1) { @@ -975,13 +1007,7 @@ findserver(dns_client_t *client) { isc_result_t result; irs_resconf_t *resconf = NULL; isc_sockaddrlist_t *nameservers; - isc_sockaddr_t *sa, *next; - uint32_t destport; - - result = parse_uint(&destport, port, 0xffff, "port"); - if (result != ISC_R_SUCCESS) { - fatal("Couldn't parse port number"); - } + isc_sockaddr_t *sa = NULL, *next = NULL; result = irs_resconf_load(mctx, "/etc/resolv.conf", &resconf); if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) { @@ -1151,6 +1177,20 @@ plus_option(char *option) { goto invalid_option; } break; + case 'n': + switch (cmd[1]) { + case 's': /* ns */ + FULLCHECK("ns"); + fulltrace = state; + if (state) { + message_trace = state; + resolve_trace = state; + } + break; + default: + goto invalid_option; + } + break; case 'r': switch (cmd[1]) { case 'o': /* root */ @@ -1234,7 +1274,7 @@ plus_option(char *option) { FULLCHECK("tcp"); use_tcp = state; break; - case 'r': /* trust */ + case 'r': FULLCHECK("trust"); showtrust = state; break; @@ -1420,6 +1460,10 @@ dash_option(char *option, char *next, bool *open_type_class) { return (value_from_next); case 'p': port = value; + result = parse_uint(&destport, port, 0xffff, "port"); + if (result != ISC_R_SUCCESS) { + fatal("Couldn't parse port number"); + } return (value_from_next); case 'q': if (curqname != NULL) { @@ -1735,10 +1779,7 @@ resolve_cb(dns_client_t *client, const dns_name_t *query_name, for (rdataset = ISC_LIST_HEAD(response_name->list); rdataset != NULL; rdataset = ISC_LIST_NEXT(rdataset, link)) { - result = printdata(rdataset, response_name); - if (result != ISC_R_SUCCESS) { - delv_log(ISC_LOG_ERROR, "print data failed"); - } + printdata(rdataset, response_name); } } @@ -1751,12 +1792,14 @@ resolve_cb(dns_client_t *client, const dns_name_t *query_name, } static void -resolve(void *arg) { - dns_client_t *client = arg; - dns_namelist_t *namelist; +run_resolve(void *arg) { + dns_client_t *client = NULL; + dns_namelist_t *namelist = NULL; unsigned int resopt; isc_result_t result; - dns_name_t *query_name; + dns_name_t *query_name = NULL; + + UNUSED(arg); namelist = isc_mem_get(mctx, sizeof(*namelist)); ISC_LIST_INIT(*namelist); @@ -1779,14 +1822,22 @@ resolve(void *arg) { resopt |= DNS_CLIENTRESOPT_TCP; } - /* Perform resolution */ - result = dns_client_resolve(client, query_name, dns_rdataclass_in, - qtype, resopt, namelist, resolve_cb); + /* Create client */ + CHECK(dns_client_create(mctx, loopmgr, netmgr, 0, tlsctx_client_cache, + &client, srcaddr4, srcaddr6)); - if (result != ISC_R_SUCCESS) { - goto cleanup; + /* Set the nameserver */ + if (server != NULL) { + addserver(client); + } else { + findserver(client); } + CHECK(setup_dnsseckeys(client, NULL)); + + /* Perform resolution */ + CHECK(dns_client_resolve(client, query_name, dns_rdataclass_in, qtype, + resopt, namelist, resolve_cb)); return; cleanup: if (!yaml) { @@ -1800,10 +1851,261 @@ cleanup: dns_client_detach(&client); } +static void +shutdown_server(void) { + if (requestmgr != NULL) { + dns_requestmgr_detach(&requestmgr); + } + if (interfacemgr != NULL) { + ns_interfacemgr_shutdown(interfacemgr); + ns_interfacemgr_detach(&interfacemgr); + } + if (dispatch != NULL) { + dns_dispatch_detach(&dispatch); + } + if (dispatchmgr != NULL) { + dns_dispatchmgr_detach(&dispatchmgr); + } + if (sctx != NULL) { + ns_server_detach(&sctx); + } + + isc_loopmgr_shutdown(loopmgr); +} + +static void +recvresponse(void *arg) { + dns_request_t *request = (dns_request_t *)arg; + dns_message_t *query = dns_request_getarg(request); + isc_result_t result = dns_request_getresult(request); + ; + dns_message_t *response = NULL; + + if (result != ISC_R_SUCCESS) { + fatal("request event result: %s", isc_result_totext(result)); + } + + dns_message_create(mctx, DNS_MESSAGE_INTENTPARSE, &response); + + result = dns_request_getresponse(request, response, + DNS_MESSAGEPARSE_PRESERVEORDER); + if (result != ISC_R_SUCCESS) { + fatal("request response failed: %s", isc_result_totext(result)); + } + if (response->rcode != dns_rcode_noerror) { + result = dns_result_fromrcode(response->rcode); + delv_log(ISC_LOG_INFO, "response code: %s", + isc_result_totext(result)); + goto cleanup; + } + + for (result = dns_message_firstname(response, DNS_SECTION_ANSWER); + result == ISC_R_SUCCESS; + result = dns_message_nextname(response, DNS_SECTION_ANSWER)) + { + dns_name_t *name = NULL; + dns_rdataset_t *rdataset = NULL; + + dns_message_currentname(response, DNS_SECTION_ANSWER, &name); + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + dns_rdataset_t rds, sigs; + int options = 0; + + /* + * The response message contains the answer the + * resolver found, but it doesn't contain the + * trust status; so if we're displaying that, we + * need to look up each rdataset in the cache and + * print that version instead. but if not, we + * can just print the rdatasets from the message. + */ + if (!showtrust) { + printdata(rdataset, name); + continue; + } + + /* do the cache lookup */ + if (rdataset->type == dns_rdatatype_rrsig) { + continue; + } + + dns_rdataset_init(&rds); + dns_rdataset_init(&sigs); + + if (cdflag) { + options |= DNS_DBFIND_PENDINGOK; + } + result = dns_view_simplefind(view, name, rdataset->type, + 0, options, false, &rds, + &sigs); + if (result == ISC_R_SUCCESS) { + printdata(&rds, name); + dns_rdataset_disassociate(&rds); + if (dns_rdataset_isassociated(&sigs)) { + printdata(&sigs, name); + dns_rdataset_disassociate(&sigs); + } + } + } + } + +cleanup: + dns_message_detach(&query); + dns_message_detach(&response); + dns_request_destroy(&request); + + view = NULL; + shutdown_server(); +} + +static isc_result_t +accept_cb(isc_nmhandle_t *handle, isc_result_t result, void *arg) { + UNUSED(handle); + UNUSED(arg); + + return (result); +} + +static void +sendquery(void *arg) { + isc_nmsocket_t *sock = (isc_nmsocket_t *)arg; + isc_sockaddr_t peer = isc_nmsocket_getaddr(sock); + isc_result_t result; + dns_message_t *message = NULL; + dns_name_t *query_name = NULL, *mname = NULL; + dns_rdataset_t *mrdataset = NULL; + dns_rdataset_t *opt = NULL; + dns_request_t *request = NULL; + + /* Construct query message */ + CHECK(convert_name(&qfn, &query_name, qname)); + + dns_message_create(mctx, DNS_MESSAGE_INTENTRENDER, &message); + message->opcode = dns_opcode_query; + message->flags = DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_AD; + if (cdflag) { + message->flags |= DNS_MESSAGEFLAG_CD; + } + message->rdclass = dns_rdataclass_in; + message->id = (dns_messageid_t)isc_random16(); + + dns_message_gettempname(message, &mname); + dns_message_gettemprdataset(message, &mrdataset); + dns_name_clone(query_name, mname); + dns_rdataset_makequestion(mrdataset, dns_rdataclass_in, qtype); + ISC_LIST_APPEND(mname->list, mrdataset, link); + dns_message_addname(message, mname, DNS_SECTION_QUESTION); + mrdataset = NULL; + mname = NULL; + + CHECK(dns_message_buildopt(message, &opt, 0, 0, DNS_MESSAGEEXTFLAG_DO, + NULL, 0)); + CHECK(dns_message_setopt(message, opt)); + + CHECK(dns_requestmgr_create(mctx, dispatchmgr, NULL, NULL, + &requestmgr)); + + CHECK(dns_request_create(requestmgr, message, NULL, &peer, NULL, NULL, + DNS_REQUESTOPT_TCP, NULL, 1, 0, 0, + isc_loop_current(loopmgr), recvresponse, + message, &request)); + return; + +cleanup: + if (message != NULL) { + if (mname != NULL) { + dns_message_puttempname(message, &mname); + } + if (mrdataset != NULL) { + dns_message_puttemprdataset(message, &mrdataset); + } + dns_message_detach(&message); + } + + shutdown_server(); +} + +static isc_result_t +matchview(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr, + dns_message_t *message, dns_aclenv_t *env, isc_result_t *sigresultp, + dns_view_t **viewp) { + UNUSED(srcaddr); + UNUSED(destaddr); + UNUSED(message); + UNUSED(env); + UNUSED(sigresultp); + + *viewp = view; + return (ISC_R_SUCCESS); +} + +static void +run_server(void *arg) { + isc_result_t result; + dns_cache_t *cache = NULL; + isc_sockaddr_t addr, any; + struct in_addr in; + + UNUSED(arg); + + inet_pton(AF_INET, "127.0.0.1", &in); + isc_sockaddr_fromin(&addr, &in, 0); + + CHECK(ns_server_create(mctx, matchview, &sctx)); + + CHECK(dns_dispatchmgr_create(mctx, netmgr, &dispatchmgr)); + isc_sockaddr_any(&any); + CHECK(dns_dispatch_createudp(dispatchmgr, &any, &dispatch)); + CHECK(ns_interfacemgr_create(mctx, sctx, loopmgr, netmgr, dispatchmgr, + NULL, false, &interfacemgr)); + + CHECK(dns_view_create(mctx, dns_rdataclass_in, "_default", &view)); + CHECK(dns_cache_create(loopmgr, dns_rdataclass_in, "", &cache)); + dns_view_setcache(view, cache, false); + dns_cache_detach(&cache); + dns_view_setdstport(view, destport); + + CHECK(dns_rootns_create(mctx, dns_rdataclass_in, NULL, &roothints)); + dns_view_sethints(view, roothints); + dns_db_detach(&roothints); + + CHECK(dns_view_initsecroots(view, mctx)); + CHECK(setup_dnsseckeys(NULL, view)); + + dns_view_setdispatchmgr(view, dispatchmgr); + CHECK(dns_view_createresolver(view, loopmgr, 1, netmgr, 0, + tlsctx_client_cache, dispatch, NULL)); + + CHECK(isc_stats_create(mctx, &resstats, dns_resstatscounter_max)); + dns_resolver_setstats(view->resolver, resstats); + isc_stats_detach(&resstats); + + CHECK(dns_rdatatypestats_create(mctx, &resquerystats)); + dns_resolver_setquerystats(view->resolver, resquerystats); + dns_stats_detach(&resquerystats); + + dns_view_freeze(view); + + ns_interface_create(interfacemgr, &addr, NULL, &ifp); + + CHECK(isc_nm_listenstreamdns(netmgr, ISC_NM_LISTEN_ONE, &addr, + ns_client_request, ifp, accept_cb, ifp, 10, + NULL, NULL, &ifp->tcplistensocket)); + ifp->flags |= NS_INTERFACEFLAG_LISTENING; + isc_job_run(loopmgr, sendquery, ifp->tcplistensocket); + + return; + +cleanup: + shutdown_server(); +} + int main(int argc, char *argv[]) { - dns_client_t *client = NULL; isc_result_t result; + isc_loop_t *loop = NULL; progname = argv[0]; preparse_args(argc, argv); @@ -1812,6 +2114,7 @@ main(int argc, char *argv[]) { argv++; isc_managers_create(&mctx, 1, &loopmgr, &netmgr); + loop = isc_loop_main(loopmgr); result = dst_lib_init(mctx, NULL); if (result != ISC_R_SUCCESS) { @@ -1824,31 +2127,22 @@ main(int argc, char *argv[]) { setup_logging(stderr); - /* Create client */ + if (fulltrace && server != NULL) { + delv_log(ISC_LOG_WARNING, + "WARNING: using internal name server mode: " + "'@%s' will be ignored", + server); + } + isc_tlsctx_cache_create(mctx, &tlsctx_client_cache); - result = dns_client_create(mctx, loopmgr, netmgr, 0, - tlsctx_client_cache, &client, srcaddr4, - srcaddr6); - if (result != ISC_R_SUCCESS) { - delv_log(ISC_LOG_ERROR, "dns_client_create: %s", - isc_result_totext(result)); - goto cleanup; - } - - /* Set the nameserver */ - if (server != NULL) { - addserver(client); - } else { - findserver(client); - } - - CHECK(setup_dnsseckeys(client)); - - isc_loop_setup(isc_loop_main(loopmgr), resolve, client); + isc_loop_setup(loop, fulltrace ? run_server : run_resolve, NULL); isc_loopmgr_run(loopmgr); cleanup: + if (tlsctx_client_cache != NULL) { + isc_tlsctx_cache_detach(&tlsctx_client_cache); + } if (trust_anchor != NULL) { isc_mem_free(mctx, trust_anchor); } @@ -1861,12 +2155,8 @@ cleanup: if (style != NULL) { dns_master_styledestroy(&style, mctx); } - if (tlsctx_client_cache != NULL) { - isc_tlsctx_cache_detach(&tlsctx_client_cache); - } isc_log_destroy(&lctx); - dst_lib_destroy(); isc_managers_destroy(&mctx, &loopmgr, &netmgr); diff --git a/bin/delv/delv.rst b/bin/delv/delv.rst index 827e3377ba..8b52f9f858 100644 --- a/bin/delv/delv.rst +++ b/bin/delv/delv.rst @@ -230,6 +230,20 @@ assign values to options like the timeout interval. They have the form This option controls whether to display the CLASS when printing a record. The default is to display the CLASS. +.. option:: +ns, +nons + + This option toggles name server mode. When this option is in use, + the ``delv`` process instantiates a full recursive resolver, and uses + that to look up the requested query name and type. Turning on this + option also activates ``+mtrace`` and ``+rtrace``, so that every + iterative query will be logged, including the full response messages + from each authoritatve server. + + This is intended to be similar to the behavior of ``dig +trace``, but + because it uses the same code as ``named``, it much more accurately + replicates the behavior of a recursive name server with a cold cache + processing a recursive query. + .. option:: +ttl, +nottl This option controls whether to display the TTL when printing a record. The From e90bb121a7df2d52a99d147301e848bab462a9c1 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Tue, 14 Feb 2023 16:56:51 -0800 Subject: [PATCH 06/14] add explicit error for +trace argument add a specific error message when someone tries to use "delv +trace" suggesting "delv +ns" instead. --- bin/delv/delv.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/bin/delv/delv.c b/bin/delv/delv.c index 617bc0f1d4..485b193d11 100644 --- a/bin/delv/delv.c +++ b/bin/delv/delv.c @@ -1275,8 +1275,19 @@ plus_option(char *option) { use_tcp = state; break; case 'r': - FULLCHECK("trust"); - showtrust = state; + switch (cmd[2]) { + case 'a': /* trace */ + FULLCHECK("trace"); + fatal("Invalid argument +trace. For " + "delegation path tracing, use +ns."); + break; + case 'u': /* trust */ + FULLCHECK("trust"); + showtrust = state; + break; + default: + goto invalid_option; + } break; case 't': /* ttl */ FULLCHECK("ttl"); From dbadc97387a466c970e54c2801c02cca51887f93 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Tue, 31 Jan 2023 13:30:12 -0800 Subject: [PATCH 07/14] on connect error, send the actual result to the caller formerly if a connection failed in dns_request, the callback function was called with ISC_R_CANCELED. change this to the actual result code so the failure is correctly reported. --- lib/dns/request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dns/request.c b/lib/dns/request.c index e3bef774b5..f78a57d7a3 100644 --- a/lib/dns/request.c +++ b/lib/dns/request.c @@ -921,7 +921,7 @@ req_connected(isc_result_t eresult, isc_region_t *region, void *arg) { req_send(request); } else { request_cancel(request); - req_sendevent(request, ISC_R_CANCELED); + req_sendevent(request, eresult); } UNLOCK(&request->requestmgr->locks[request->hash]); From 8ce33dca6a5c3a57dab62e56f7e974a41b2a8d1d Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Tue, 31 Jan 2023 13:30:12 -0800 Subject: [PATCH 08/14] change the log level of "resolver priming query complete" this log message, formerly at level INFO, is now DEBUG(1), so it won't be printed when running "delv +ns +nortrace". --- lib/dns/resolver.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 1299b9f480..10ba6e807d 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -9974,7 +9974,7 @@ prime_done(void *arg) { REQUIRE(VALID_RESOLVER(res)); isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER, - DNS_LOGMODULE_RESOLVER, ISC_LOG_INFO, + DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(1), "resolver priming query complete: %s", isc_result_totext(resp->result)); From 32189f6a593f224354f17d8032357ec84388ac5d Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Tue, 14 Feb 2023 17:28:55 -0800 Subject: [PATCH 09/14] send delv +ns output to stdout normally, the only output of delv that is sent to stdout is the final answer to the query; all other output is sent to stderr. this seems undesirable for delv +ns, which will only be used to see the process of finding the answer. so, for that case, we now send all the logging to stdout. --- bin/delv/delv.c | 12 ++++++++---- bin/delv/delv.rst | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/bin/delv/delv.c b/bin/delv/delv.c index 485b193d11..ad747368d5 100644 --- a/bin/delv/delv.c +++ b/bin/delv/delv.c @@ -88,7 +88,7 @@ #define MAXNAME (DNS_NAME_MAXTEXT + 1) /* Variables used internally by delv. */ -char *progname; +char *progname = NULL; static isc_mem_t *mctx = NULL; static isc_log_t *lctx = NULL; static dns_view_t *view = NULL; @@ -98,6 +98,7 @@ static dns_dispatch_t *dispatch = NULL; static dns_db_t *roothints = NULL; static isc_stats_t *resstats = NULL; static dns_stats_t *resquerystats = NULL; +static FILE *logfp = NULL; /* Managers */ static isc_nm_t *netmgr = NULL; @@ -1185,6 +1186,7 @@ plus_option(char *option) { if (state) { message_trace = state; resolve_trace = state; + logfp = stdout; } break; default: @@ -1889,7 +1891,6 @@ recvresponse(void *arg) { dns_request_t *request = (dns_request_t *)arg; dns_message_t *query = dns_request_getarg(request); isc_result_t result = dns_request_getresult(request); - ; dns_message_t *response = NULL; if (result != ISC_R_SUCCESS) { @@ -1967,7 +1968,7 @@ cleanup: dns_message_detach(&response); dns_request_destroy(&request); - view = NULL; + dns_view_detach(&view); shutdown_server(); } @@ -2018,6 +2019,7 @@ sendquery(void *arg) { CHECK(dns_requestmgr_create(mctx, dispatchmgr, NULL, NULL, &requestmgr)); + dns_view_attach(view, &(dns_view_t *){ NULL }); CHECK(dns_request_create(requestmgr, message, NULL, &peer, NULL, NULL, DNS_REQUESTOPT_TCP, NULL, 1, 0, 0, isc_loop_current(loopmgr), recvresponse, @@ -2119,6 +2121,8 @@ main(int argc, char *argv[]) { isc_loop_t *loop = NULL; progname = argv[0]; + logfp = stderr; + preparse_args(argc, argv); argc--; @@ -2136,7 +2140,7 @@ main(int argc, char *argv[]) { CHECK(setup_style()); - setup_logging(stderr); + setup_logging(logfp); if (fulltrace && server != NULL) { delv_log(ISC_LOG_WARNING, diff --git a/bin/delv/delv.rst b/bin/delv/delv.rst index 8b52f9f858..5d91fe75fb 100644 --- a/bin/delv/delv.rst +++ b/bin/delv/delv.rst @@ -237,12 +237,14 @@ assign values to options like the timeout interval. They have the form that to look up the requested query name and type. Turning on this option also activates ``+mtrace`` and ``+rtrace``, so that every iterative query will be logged, including the full response messages - from each authoritatve server. + from each authoritatve server. These logged messages will be written + to ``stdout`` rather than ``stderr`` as usual, so that the full trace + can be captured more easily. This is intended to be similar to the behavior of ``dig +trace``, but because it uses the same code as ``named``, it much more accurately replicates the behavior of a recursive name server with a cold cache - processing a recursive query. + that is processing a recursive query. .. option:: +ttl, +nottl From 152d25668ecdab7960ad98ea3fafe96ff224dc99 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Wed, 1 Feb 2023 23:19:36 -0800 Subject: [PATCH 10/14] add "delv +strace" "delv +strace" is similar to "delv +mtrace", but sets the logging level to DEBUG(11) instead of DEBUG(10), so that packets sent will be logged along with packets received. "delv +ns" turns this option on by default. --- bin/delv/delv.c | 21 ++++++++++++++++++--- bin/delv/delv.rst | 32 ++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/bin/delv/delv.c b/bin/delv/delv.c index ad747368d5..843690c405 100644 --- a/bin/delv/delv.c +++ b/bin/delv/delv.c @@ -129,7 +129,7 @@ static bool showcomments = true, showdnssec = true, showtrust = true, yaml = false, fulltrace = false; static bool resolve_trace = false, validator_trace = false, - message_trace = false; + message_trace = false, send_trace = false; static bool use_ipv4 = true, use_ipv6 = true; @@ -228,6 +228,8 @@ usage(void) { " +[no]short (Short form answer)\n" " +[no]split=## (Split hex/base64 fields " "into chunks)\n" + " +[no]strace (Trace messages " + "sent)\n" " +[no]tcp (TCP mode)\n" " +[no]ttl (Control display of ttls " "in records)\n" @@ -307,6 +309,7 @@ setup_logging(FILE *errout) { isc_result_t result; isc_logdestination_t destination; isc_logconfig_t *logconfig = NULL; + int packetlevel = 10; isc_log_create(mctx, &lctx, &logconfig); isc_log_registercategories(lctx, categories); @@ -359,9 +362,12 @@ setup_logging(FILE *errout) { } } - if (message_trace && loglevel < 10) { + if (send_trace) { + packetlevel = 11; + } + if ((message_trace || send_trace) && loglevel < packetlevel) { isc_log_createchannel(logconfig, "messages", ISC_LOG_TOFILEDESC, - ISC_LOG_DEBUG(10), &destination, + ISC_LOG_DEBUG(packetlevel), &destination, ISC_LOG_PRINTPREFIX); result = isc_log_usechannel(logconfig, "messages", @@ -1165,6 +1171,7 @@ plus_option(char *option) { case 'm': switch (cmd[1]) { case 't': /* mtrace */ + FULLCHECK("mtrace"); message_trace = state; if (state) { resolve_trace = state; @@ -1185,6 +1192,7 @@ plus_option(char *option) { fulltrace = state; if (state) { message_trace = state; + send_trace = state; resolve_trace = state; logfp = stdout; } @@ -1262,6 +1270,13 @@ plus_option(char *option) { fatal("Couldn't parse split"); } break; + case 't': /* strace */ + FULLCHECK("strace"); + send_trace = state; + if (state) { + message_trace = state; + } + break; default: goto invalid_option; } diff --git a/bin/delv/delv.rst b/bin/delv/delv.rst index 5d91fe75fb..67c082e704 100644 --- a/bin/delv/delv.rst +++ b/bin/delv/delv.rst @@ -235,8 +235,8 @@ assign values to options like the timeout interval. They have the form This option toggles name server mode. When this option is in use, the ``delv`` process instantiates a full recursive resolver, and uses that to look up the requested query name and type. Turning on this - option also activates ``+mtrace`` and ``+rtrace``, so that every - iterative query will be logged, including the full response messages + option also activates ``+mtrace``, ``+strace`` and ``+rtrace``, so that + every iterative query will be logged, including the full response messages from each authoritatve server. These logged messages will be written to ``stdout`` rather than ``stderr`` as usual, so that the full trace can be captured more easily. @@ -253,11 +253,11 @@ assign values to options like the timeout interval. They have the form .. option:: +rtrace, +nortrace - This option toggles resolver fetch logging. This reports the name and type of each - query sent by :program:`delv` in the process of carrying out the resolution - and validation process, including the original query - and all subsequent queries to follow CNAMEs and to establish a chain - of trust for DNSSEC validation. + This option toggles resolver fetch logging. This reports the name and + type of each query sent by :program:`delv` in the process of carrying + out the resolution and validation process, including the original query + and all subsequent queries to follow CNAMEs and to establish a chain of + trust for DNSSEC validation. This is equivalent to setting the debug level to 1 in the "resolver" logging category. Setting the systemwide debug level to 1 using the @@ -266,15 +266,27 @@ assign values to options like the timeout interval. They have the form .. option:: +mtrace, +nomtrace - This option toggles message logging. This produces a detailed dump of the - responses received by :program:`delv` in the process of carrying out the - resolution and validation process. + This option toggles logging of messages received. This produces + a detailed dump of the responses received by :program:`delv` in the + process of carrying out the resolution and validation process. This is equivalent to setting the debug level to 10 for the "packets" module of the "resolver" logging category. Setting the systemwide debug level to 10 using the :option:`-d` option produces the same output, but affects other logging categories as well. +.. option:: +strace, +nostrace + + This option toggles logging of messages sent. This produces a detailed + dump of the queries sent by :program:`delv` in the process of carrying + out the resolution and validation process. Turning on this option + also activates ``+mtrace``. + + This is equivalent to setting the debug level to 11 for the "packets" + module of the "resolver" logging category. Setting the systemwide + debug level to 11 using the :option:`-d` option produces the same + output, but affects other logging categories as well. + .. option:: +vtrace, +novtrace This option toggles validation logging. This shows the internal process of the From c277df8c1f58e3b591d857dd253da0d06292f5c3 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Fri, 3 Mar 2023 00:46:36 -0800 Subject: [PATCH 11/14] add "delv +qmin" add an option to enable or disable QNAME minimization in delv's internal resolver. --- bin/delv/delv.c | 29 +++++++++++++++++++++++++++++ bin/delv/delv.rst | 7 +++++++ 2 files changed, 36 insertions(+) diff --git a/bin/delv/delv.c b/bin/delv/delv.c index 843690c405..eba5483c92 100644 --- a/bin/delv/delv.c +++ b/bin/delv/delv.c @@ -134,6 +134,7 @@ static bool resolve_trace = false, validator_trace = false, static bool use_ipv4 = true, use_ipv6 = true; static bool cdflag = false, no_sigs = false, root_validation = true; +static bool qmin = false, qmin_strict = false; static bool use_tcp = false; @@ -218,6 +219,8 @@ usage(void) { "server)\n" " +[no]multiline (Print records in an " "expanded format)\n" + " +[no]qmin[=mode] (QNAME minimization: " + "relaxed or strict)\n" " +[no]root (DNSSEC validation trust " "anchor)\n" " +[no]rrcomments (Control display of " @@ -1201,6 +1204,25 @@ plus_option(char *option) { goto invalid_option; } break; + case 'q': /* qmin */ + FULLCHECK("qmin"); + if (state) { + if (value == NULL || strcasecmp(value, "relaxed") == 0) + { + qmin = true; + } else if (strcasecmp(value, "strict") == 0) { + qmin = true; + qmin_strict = true; + } else { + fatal("Invalid qmin option '%s': " + "use 'relaxed' or 'strict'\n", + value); + } + } else { + qmin = false; + qmin_strict = false; + } + break; case 'r': switch (cmd[1]) { case 'o': /* root */ @@ -1337,6 +1359,10 @@ plus_option(char *option) { fprintf(stderr, "Invalid option: +%s\n", option); usage(); } + + if (qmin && !fulltrace) { + fatal("'+qmin' cannot be used without '+ns'"); + } return; } @@ -2099,6 +2125,9 @@ run_server(void *arg) { dns_view_sethints(view, roothints); dns_db_detach(&roothints); + view->qminimization = qmin; + view->qmin_strict = qmin_strict; + CHECK(dns_view_initsecroots(view, mctx)); CHECK(setup_dnsseckeys(NULL, view)); diff --git a/bin/delv/delv.rst b/bin/delv/delv.rst index 67c082e704..6cad0c0af1 100644 --- a/bin/delv/delv.rst +++ b/bin/delv/delv.rst @@ -246,6 +246,13 @@ assign values to options like the timeout interval. They have the form replicates the behavior of a recursive name server with a cold cache that is processing a recursive query. +.. option:: +qmin[=MODE], +noqmin + + When used with ``+ns``, this option enables QNAME minimization mode. + Valid options of MODE are ``relaxed`` and ``strict``. By default, + QNAME minimization is disabled. If ``+qmin`` is specified but MODE + is omitted, then ``relaxed`` mode will be used. + .. option:: +ttl, +nottl This option controls whether to display the TTL when printing a record. The From 15fd74f4664369726be9c887020f02393312101f Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Fri, 24 Mar 2023 17:50:32 -0700 Subject: [PATCH 12/14] add "delv +hint" Add the ablity to specify a hints file from which to load root name server addresses, so that "delv +ns" can be used with test name servers. --- bin/delv/delv.c | 28 +++++++++++++++++++++++++++- bin/delv/delv.rst | 7 +++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/bin/delv/delv.c b/bin/delv/delv.c index eba5483c92..357bdfd1ad 100644 --- a/bin/delv/delv.c +++ b/bin/delv/delv.c @@ -120,6 +120,7 @@ static char *curqname = NULL, *qname = NULL; static bool classset = false; static dns_rdatatype_t qtype = dns_rdatatype_none; static bool typeset = false; +static const char *hintfile = NULL; static unsigned int styleflags = 0; static uint32_t splitwidth = 0xffffffff; @@ -1171,6 +1172,22 @@ plus_option(char *option) { goto invalid_option; } break; + case 'h': + switch (cmd[1]) { + case 'i': /* hint */ + if (state) { + if (value == NULL) { + fatal("+hint: must specify hint file"); + } + hintfile = value; + } else { + hintfile = NULL; + } + break; + default: + goto invalid_option; + } + break; case 'm': switch (cmd[1]) { case 't': /* mtrace */ @@ -2121,7 +2138,7 @@ run_server(void *arg) { dns_cache_detach(&cache); dns_view_setdstport(view, destport); - CHECK(dns_rootns_create(mctx, dns_rdataclass_in, NULL, &roothints)); + CHECK(dns_rootns_create(mctx, dns_rdataclass_in, hintfile, &roothints)); dns_view_sethints(view, roothints); dns_db_detach(&roothints); @@ -2156,6 +2173,9 @@ run_server(void *arg) { return; cleanup: + if (view != NULL) { + dns_view_detach(&view); + } shutdown_server(); } @@ -2186,6 +2206,12 @@ main(int argc, char *argv[]) { setup_logging(logfp); + if (!fulltrace && hintfile != NULL) { + delv_log(ISC_LOG_WARNING, + "WARNING: not using internal name server mode, " + "hint file will be ignored"); + } + if (fulltrace && server != NULL) { delv_log(ISC_LOG_WARNING, "WARNING: using internal name server mode: " diff --git a/bin/delv/delv.rst b/bin/delv/delv.rst index 6cad0c0af1..61d1b701a9 100644 --- a/bin/delv/delv.rst +++ b/bin/delv/delv.rst @@ -230,6 +230,13 @@ assign values to options like the timeout interval. They have the form This option controls whether to display the CLASS when printing a record. The default is to display the CLASS. +.. option:: +hint=FILE, +nohint + + This option specifies a filename from which to load root hints; + this will be used to find the root name servers when name server + mode (``delv +ns``) is in use. If the option is not specified, + built-in root hints will be used. + .. option:: +ns, +nons This option toggles name server mode. When this option is in use, From 8806abcaaa5aea1f9e3c386b72f57a22c0286a12 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Mon, 27 Mar 2023 15:42:09 -0700 Subject: [PATCH 13/14] test "delv +ns" add tests for "delv +ns", with and without +qmin and with and without validation. --- bin/tests/system/digdelv/clean.sh | 9 +++-- .../digdelv/ns1/{root.db => root.db.in} | 0 bin/tests/system/digdelv/ns1/sign.sh | 31 +++++++++++++++ bin/tests/system/digdelv/setup.sh | 2 +- bin/tests/system/digdelv/tests.sh | 38 ++++++++++++++++++- 5 files changed, 74 insertions(+), 6 deletions(-) rename bin/tests/system/digdelv/ns1/{root.db => root.db.in} (100%) create mode 100644 bin/tests/system/digdelv/ns1/sign.sh diff --git a/bin/tests/system/digdelv/clean.sh b/bin/tests/system/digdelv/clean.sh index 77e467a499..a64603600c 100644 --- a/bin/tests/system/digdelv/clean.sh +++ b/bin/tests/system/digdelv/clean.sh @@ -13,7 +13,7 @@ set -e -rm -f ./*/anchor.* +rm -f ./anchor.* ./*/anchor.* rm -f ./*/named.conf rm -f ./*/named.memstats rm -f ./*/named.run @@ -28,9 +28,10 @@ rm -f ./dig.out.nn.* rm -f ./host.out.test* rm -f ./ns*/managed-keys.bind* rm -f ./ns*/named.lock -rm -f ./ns2/dsset-example. -rm -f ./ns2/dsset-example.tld. -rm -f ./ns2/example.db ./ns2/K* ./ns2/keyid ./ns2/keydata +rm -f ./ns*/K* ./ns*/keyid ./ns*/keydata +rm -f ./ns1/root.db +rm -f ./ns*/dsset-* +rm -f ./ns2/example.db rm -f ./ns2/example.tld.db rm -f ./nslookup.out.test* rm -f ./nsupdate.out.test* diff --git a/bin/tests/system/digdelv/ns1/root.db b/bin/tests/system/digdelv/ns1/root.db.in similarity index 100% rename from bin/tests/system/digdelv/ns1/root.db rename to bin/tests/system/digdelv/ns1/root.db.in diff --git a/bin/tests/system/digdelv/ns1/sign.sh b/bin/tests/system/digdelv/ns1/sign.sh new file mode 100644 index 0000000000..2651c191c1 --- /dev/null +++ b/bin/tests/system/digdelv/ns1/sign.sh @@ -0,0 +1,31 @@ +#!/bin/sh -e + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# 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 https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# shellcheck source=conf.sh +. ../../conf.sh + +set -e + +(cd ../ns2 && $SHELL sign.sh ) + +cp "../ns2/dsset-example." . + +ksk=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone .) + +cp root.db.in root.db + +"$SIGNER" -Sgz -f root.db -o . root.db.in > /dev/null 2>&1 + +keyfile_to_key_id "$ksk" > keyid +grep -Ev '^;' < "$ksk.key" | cut -f 7- -d ' ' > keydata +keyfile_to_initial_keys "$ksk" > anchor.dnskey diff --git a/bin/tests/system/digdelv/setup.sh b/bin/tests/system/digdelv/setup.sh index ca63977593..92dd6bf3b2 100644 --- a/bin/tests/system/digdelv/setup.sh +++ b/bin/tests/system/digdelv/setup.sh @@ -20,4 +20,4 @@ copy_setports ns1/named.conf.in ns1/named.conf copy_setports ns2/named.conf.in ns2/named.conf copy_setports ns3/named.conf.in ns3/named.conf -cd ns2 && $SHELL sign.sh +cd ns1 && $SHELL sign.sh diff --git a/bin/tests/system/digdelv/tests.sh b/bin/tests/system/digdelv/tests.sh index 9e82298f35..d222e57d10 100644 --- a/bin/tests/system/digdelv/tests.sh +++ b/bin/tests/system/digdelv/tests.sh @@ -54,7 +54,7 @@ check_ttl_range() { return $result } -# using delv insecure mode as not testing dnssec here +# use delv insecure mode by default, as we're mostly not testing dnssec delv_with_opts() { "$DELV" +noroot -p "$PORT" "$@" } @@ -1404,6 +1404,42 @@ if [ -x "$DELV" ] ; then if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status+ret)) + n=$((n+1)) + echo_i "checking delv +ns (no validation) ($n)" + ret=0 + delv_with_opts -i +ns +hint=../common/root.hint a a.example > delv.out.test$n || ret=1 + grep -q '; authoritative' delv.out.test$n || ret=1 + grep -q '_.example' delv.out.test$n && ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status+ret)) + + n=$((n+1)) + echo_i "checking delv +ns +qmin (no validation) ($n)" + ret=0 + delv_with_opts -i +ns +qmin +hint=../common/root.hint a a.example > delv.out.test$n || ret=1 + grep -q '; authoritative' delv.out.test$n || ret=1 + grep -q '_.example' delv.out.test$n || ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status+ret)) + + n=$((n+1)) + echo_i "checking delv +ns (with validation) ($n)" + ret=0 + delv_with_opts -a ns1/anchor.dnskey +root +ns +hint=../common/root.hint a a.example > delv.out.test$n || ret=1 + grep -q '; fully validated' delv.out.test$n || ret=1 + grep -q '_.example' delv.out.test$n && ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status+ret)) + + n=$((n+1)) + echo_i "checking delv +ns +qmin (with validation) ($n)" + ret=0 + delv_with_opts -a ns1/anchor.dnskey +root +ns +qmin +hint=../common/root.hint a a.example > delv.out.test$n || ret=1 + grep -q '; fully validated' delv.out.test$n || ret=1 + grep -q '_.example' delv.out.test$n || ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status+ret)) + else echo_i "$DELV is needed, so skipping these delv tests" fi From 155f6a2996ffe3ec4f535d8d0afb27c3f83d3027 Mon Sep 17 00:00:00 2001 From: Evan Hunt Date: Wed, 1 Feb 2023 23:38:04 -0800 Subject: [PATCH 14/14] CHANGES and release note for [GL #3842] --- CHANGES | 10 ++++++++++ doc/notes/notes-current.rst | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/CHANGES b/CHANGES index 5925f440b9..c8f60c16be 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,13 @@ +6130. [func] The new "delv +ns" option activates name server mode, + in which delv sets up an internal recursive + resolver and uses that, rather than an external + server, to look up the requested data. All messages + sent and received during the resolution and + validation process are logged. This can be used in + place of "dig +trace"; it more accurately + replicates the behavior of named when resolving + a query. [GL #3842] + 6129. [cleanup] Value stored to 'source' during its initialization is never read. [GL #3965] diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index 438b47c6dd..208086c7e1 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -23,6 +23,17 @@ New Features - BIND now depends on ``liburcu``, Userspace RCU, for lock-free data structures. :gl:`#3934` +- The new ``delv +ns`` option activates name server mode, in which ``delv`` + sets up an internal recursive resolver and uses that, rather than an + external server, to look up the requested query name and type. All messages + sent and received during the resolution and validation process are logged. + This can be used in place of ``dig +trace``: it more accurately + reproduces the behavior of ``named`` when resolving a query. + + The log message ``resolver priming query complete`` was moved from the + INFO log level to the DEBUG(1) log level, to prevent ``delv`` from + emitting that message when setting up its internal resolver. :gl:`#3842` + Removed Features ~~~~~~~~~~~~~~~~