From 1cee5579930807d504fc412ef462bcd287b30cfb Mon Sep 17 00:00:00 2001 From: Diego Fronza Date: Thu, 10 Sep 2020 15:09:14 -0300 Subject: [PATCH 1/5] Fix transfer of glue records in stub zones if master has minimal-responses set Stub zones don't make use of AXFR/IXFR for the transfering of zone data, instead, a single query is issued to the master asking for their nameserver records (NS). That works fine unless master is configured with 'minimal-responses' set to yes, in which case glue records are not provided by master in the answer with nameservers authoritative for the zone, leaving stub zones with incomplete databases. This commit fix this problem in a simple way, when the answer with the authoritative nameservers is received from master (stub_callback), for each nameserver listed (save_nsrrset), a A and AAAA records for the name is verified in the additional section, and if not present a query is created to resolve the corresponsing missing glue. A struct 'stub_cb_args' was added to keep relevant information for performing a query, like TSIG key, udp size, dscp value, etc, this information is borrowed from, and created within function 'ns_query', where the resolving of nameserver from master starts. A new field was added to the struct 'dns_stub', an atomic integer, namely pending_requests, which is used to keep how many queries are created when resolving nameserver addresses that were missing in the glue. When the value of pending_requests is zero we know we can release resources, adjust zone timers, dump to zone file, etc. --- lib/dns/zone.c | 645 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 514 insertions(+), 131 deletions(-) diff --git a/lib/dns/zone.c b/lib/dns/zone.c index b8cd90b129..db94e130ac 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -614,6 +614,7 @@ struct dns_stub { dns_zone_t *zone; dns_db_t *db; dns_dbversion_t *version; + atomic_uint_fast32_t pending_requests; }; /*% @@ -937,6 +938,22 @@ struct ssevent { uint32_t serial; }; +struct stub_cb_args { + dns_stub_t *stub; + dns_tsigkey_t *tsig_key; + isc_dscp_t dscp; + uint16_t udpsize; + int timeout; + bool reqnsid; +}; + +struct stub_glue_request { + dns_request_t *request; + dns_name_t name; + struct stub_cb_args *args; + bool ipv4; +}; + /*% * Increment resolver-related statistics counters. Zone must be locked. */ @@ -12491,9 +12508,395 @@ cleanup1: /*** *** Private ***/ +static inline isc_result_t +create_query(dns_zone_t *zone, dns_rdatatype_t rdtype, dns_name_t *name, + dns_message_t **messagep) { + dns_message_t *message = NULL; + dns_name_t *qname = NULL; + dns_rdataset_t *qrdataset = NULL; + isc_result_t result; + + dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER, &message); + + message->opcode = dns_opcode_query; + message->rdclass = zone->rdclass; + + result = dns_message_gettempname(message, &qname); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_message_gettemprdataset(message, &qrdataset); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* + * Make question. + */ + dns_name_init(qname, NULL); + dns_name_clone(name, qname); + dns_rdataset_makequestion(qrdataset, zone->rdclass, rdtype); + ISC_LIST_APPEND(qname->list, qrdataset, link); + dns_message_addname(message, qname, DNS_SECTION_QUESTION); + + *messagep = message; + return (ISC_R_SUCCESS); + +cleanup: + if (qname != NULL) { + dns_message_puttempname(message, &qname); + } + if (qrdataset != NULL) { + dns_message_puttemprdataset(message, &qrdataset); + } + dns_message_detach(&message); + return (result); +} + +static isc_result_t +add_opt(dns_message_t *message, uint16_t udpsize, bool reqnsid, + bool reqexpire) { + isc_result_t result; + dns_rdataset_t *rdataset = NULL; + dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS]; + int count = 0; + + /* Set EDNS options if applicable. */ + if (reqnsid) { + INSIST(count < DNS_EDNSOPTIONS); + ednsopts[count].code = DNS_OPT_NSID; + ednsopts[count].length = 0; + ednsopts[count].value = NULL; + count++; + } + if (reqexpire) { + INSIST(count < DNS_EDNSOPTIONS); + ednsopts[count].code = DNS_OPT_EXPIRE; + ednsopts[count].length = 0; + ednsopts[count].value = NULL; + count++; + } + result = dns_message_buildopt(message, &rdataset, 0, udpsize, 0, + ednsopts, count); + if (result != ISC_R_SUCCESS) { + return (result); + } + + return (dns_message_setopt(message, rdataset)); +} + +/* + * Called when stub zone update is finished. + * Update zone refresh, retry, expire values accordingly with + * SOA received from master, sync database to file, restart + * zone management timer. + */ +static void +stub_finish_zone_update(dns_stub_t *stub, isc_time_t now) { + uint32_t refresh, retry, expire; + isc_result_t result; + isc_interval_t i; + unsigned int soacount; + dns_zone_t *zone = stub->zone; + + /* + * Tidy up. + */ + dns_db_closeversion(stub->db, &stub->version, true); + ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); + if (zone->db == NULL) { + zone_attachdb(zone, stub->db); + } + result = zone_get_from_db(zone, zone->db, NULL, &soacount, NULL, + &refresh, &retry, &expire, NULL, NULL); + if (result == ISC_R_SUCCESS && soacount > 0U) { + zone->refresh = RANGE(refresh, zone->minrefresh, + zone->maxrefresh); + zone->retry = RANGE(retry, zone->minretry, zone->maxretry); + zone->expire = RANGE(expire, zone->refresh + zone->retry, + DNS_MAX_EXPIRE); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HAVETIMERS); + } + ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); + dns_db_detach(&stub->db); + + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); + DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED); + DNS_ZONE_JITTER_ADD(&now, zone->refresh, &zone->refreshtime); + isc_interval_set(&i, zone->expire, 0); + DNS_ZONE_TIME_ADD(&now, zone->expire, &zone->expiretime); + + if (zone->masterfile != NULL) { + zone_needdump(zone, 0); + } + + zone_settimer(zone, &now); +} + +/* + * Process answers for A and AAAA queries when + * resolving nameserver addresses for which glue + * was missing in a previous answer for a NS query. + */ +static void +stub_glue_response_cb(isc_task_t *task, isc_event_t *event) { + const char me[] = "stub_glue_response_cb"; + dns_requestevent_t *revent = (dns_requestevent_t *)event; + dns_stub_t *stub = NULL; + dns_message_t *msg = NULL; + dns_zone_t *zone = NULL; + char master[ISC_SOCKADDR_FORMATSIZE]; + char source[ISC_SOCKADDR_FORMATSIZE]; + uint32_t addr_count, cnamecnt; + isc_result_t result; + isc_time_t now; + struct stub_glue_request *request; + struct stub_cb_args *cb_args; + dns_rdataset_t *addr_rdataset = NULL; + dns_dbnode_t *node = NULL; + + UNUSED(task); + + request = revent->ev_arg; + cb_args = request->args; + stub = cb_args->stub; + INSIST(DNS_STUB_VALID(stub)); + + zone = stub->zone; + + ENTER; + + TIME_NOW(&now); + + LOCK_ZONE(zone); + + if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXITING)) { + zone_debuglog(zone, me, 1, "exiting"); + goto cleanup; + } + + isc_sockaddr_format(&zone->masteraddr, master, sizeof(master)); + isc_sockaddr_format(&zone->sourceaddr, source, sizeof(source)); + + if (revent->result != ISC_R_SUCCESS) { + dns_zonemgr_unreachableadd(zone->zmgr, &zone->masteraddr, + &zone->sourceaddr, &now); + dns_zone_log(zone, ISC_LOG_INFO, + "could not refresh stub from master %s" + " (source %s): %s", + master, source, dns_result_totext(revent->result)); + goto cleanup; + } + + dns_message_create(zone->mctx, DNS_MESSAGE_INTENTPARSE, &msg); + result = dns_request_getresponse(revent->request, msg, 0); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: unable to parse response (%s)", + isc_result_totext(result)); + goto cleanup; + } + + /* + * Unexpected rcode. + */ + if (msg->rcode != dns_rcode_noerror) { + char rcode[128]; + isc_buffer_t rb; + + isc_buffer_init(&rb, rcode, sizeof(rcode)); + (void)dns_rcode_totext(msg->rcode, &rb); + + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "unexpected rcode (%.*s) from %s (source %s)", + (int)rb.used, rcode, master, source); + goto cleanup; + } + + /* + * We need complete messages. + */ + if ((msg->flags & DNS_MESSAGEFLAG_TC) != 0) { + if (dns_request_usedtcp(revent->request)) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: truncated TCP " + "response from master %s (source %s)", + master, source); + } + goto cleanup; + } + + /* + * If non-auth log. + */ + if ((msg->flags & DNS_MESSAGEFLAG_AA) == 0) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "non-authoritative answer from " + "master %s (source %s)", + master, source); + goto cleanup; + } + + /* + * Sanity checks. + */ + cnamecnt = message_count(msg, DNS_SECTION_ANSWER, dns_rdatatype_cname); + addr_count = message_count(msg, DNS_SECTION_ANSWER, + request->ipv4 ? dns_rdatatype_a + : dns_rdatatype_aaaa); + + if (cnamecnt != 0) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: unexpected CNAME response " + "from master %s (source %s)", + master, source); + goto cleanup; + } + + if (addr_count == 0) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: no %s records in response " + "from master %s (source %s)", + request->ipv4 ? "A" : "AAAA", master, source); + goto cleanup; + } + /* + * Extract A or AAAA RRset from message. + */ + result = dns_message_findname(msg, DNS_SECTION_ANSWER, &request->name, + request->ipv4 ? dns_rdatatype_a + : dns_rdatatype_aaaa, + dns_rdatatype_none, NULL, &addr_rdataset); + if (result != ISC_R_SUCCESS) { + if (result != DNS_R_NXDOMAIN && result != DNS_R_NXRRSET) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(&request->name, namebuf, + sizeof(namebuf)); + dns_zone_log( + zone, ISC_LOG_INFO, + "refreshing stub: dns_message_findname(%s/%s) " + "failed (%s)", + namebuf, request->ipv4 ? "A" : "AAAA", + isc_result_totext(result)); + } + goto cleanup; + } + + result = dns_db_findnode(stub->db, &request->name, true, &node); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "dns_db_findnode() failed: %s", + dns_result_totext(result)); + goto cleanup; + } + + result = dns_db_addrdataset(stub->db, node, stub->version, 0, + addr_rdataset, 0, NULL); + if (result != ISC_R_SUCCESS) { + dns_zone_log(zone, ISC_LOG_INFO, + "refreshing stub: " + "dns_db_addrdataset() failed: %s", + dns_result_totext(result)); + } + dns_db_detachnode(stub->db, &node); + +cleanup: + if (msg != NULL) { + dns_message_detach(&msg); + } + isc_event_free(&event); + dns_name_free(&request->name, zone->mctx); + dns_request_destroy(&request->request); + isc_mem_put(zone->mctx, request, sizeof(*request)); + + /* If last request, release all related resources */ + if (atomic_fetch_sub_release(&stub->pending_requests, 1) == 1) { + isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args)); + stub_finish_zone_update(stub, now); + UNLOCK_ZONE(zone); + stub->magic = 0; + dns_zone_idetach(&stub->zone); + INSIST(stub->db == NULL); + INSIST(stub->version == NULL); + isc_mem_put(stub->mctx, stub, sizeof(*stub)); + } else { + UNLOCK_ZONE(zone); + } +} + +/* + * Create and send an A or AAAA query to the master + * server of the stub zone given. + */ +static isc_result_t +stub_request_nameserver_address(struct stub_cb_args *args, bool ipv4, + const dns_name_t *name) { + dns_message_t *message = NULL; + dns_zone_t *zone; + isc_result_t result; + struct stub_glue_request *request; + + zone = args->stub->zone; + request = isc_mem_get(zone->mctx, sizeof(*request)); + request->request = NULL; + request->args = args; + request->name = (dns_name_t)DNS_NAME_INITEMPTY; + request->ipv4 = ipv4; + dns_name_dup(name, zone->mctx, &request->name); + + result = create_query(zone, ipv4 ? dns_rdatatype_a : dns_rdatatype_aaaa, + &request->name, &message); + INSIST(result == ISC_R_SUCCESS); + + if (!DNS_ZONE_FLAG(zone, DNS_ZONEFLG_NOEDNS)) { + result = add_opt(message, args->udpsize, args->reqnsid, false); + if (result != ISC_R_SUCCESS) { + zone_debuglog(zone, "stub_send_query", 1, + "unable to add opt record: %s", + dns_result_totext(result)); + goto fail; + } + } + + atomic_fetch_add_release(&args->stub->pending_requests, 1); + + result = dns_request_createvia( + zone->view->requestmgr, message, &zone->sourceaddr, + &zone->masteraddr, args->dscp, DNS_REQUESTOPT_TCP, + args->tsig_key, args->timeout * 3, args->timeout, 0, zone->task, + stub_glue_response_cb, request, &request->request); + + if (result != ISC_R_SUCCESS) { + INSIST(atomic_fetch_sub_release(&args->stub->pending_requests, + 1) > 1); + zone_debuglog(zone, "stub_send_query", 1, + "dns_request_createvia() failed: %s", + dns_result_totext(result)); + goto fail; + } + + dns_message_detach(&message); + + return (ISC_R_SUCCESS); + +fail: + dns_name_free(&request->name, zone->mctx); + isc_mem_put(zone->mctx, request, sizeof(*request)); + + if (message != NULL) { + dns_message_detach(&message); + } + + return (result); +} static inline isc_result_t -save_nsrrset(dns_message_t *message, dns_name_t *name, dns_db_t *db, +save_nsrrset(dns_message_t *message, dns_name_t *name, + struct stub_cb_args *cb_args, dns_db_t *db, dns_dbversion_t *version) { dns_rdataset_t *nsrdataset = NULL; dns_rdataset_t *rdataset = NULL; @@ -12501,6 +12904,14 @@ save_nsrrset(dns_message_t *message, dns_name_t *name, dns_db_t *db, dns_rdata_ns_t ns; isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; + bool has_glue = false; + dns_name_t *ns_name; + /* + * List of NS entries in answer, keep names that will be used + * to resolve missing A/AAAA glue for each entry. + */ + dns_namelist_t ns_list; + ISC_LIST_INIT(ns_list); /* * Extract NS RRset from message. @@ -12509,7 +12920,7 @@ save_nsrrset(dns_message_t *message, dns_name_t *name, dns_db_t *db, dns_rdatatype_ns, dns_rdatatype_none, NULL, &nsrdataset); if (result != ISC_R_SUCCESS) { - goto fail; + goto done; } /* @@ -12517,12 +12928,12 @@ save_nsrrset(dns_message_t *message, dns_name_t *name, dns_db_t *db, */ result = dns_db_findnode(db, name, true, &node); if (result != ISC_R_SUCCESS) { - goto fail; + goto done; } result = dns_db_addrdataset(db, node, version, 0, nsrdataset, 0, NULL); dns_db_detachnode(db, &node); if (result != ISC_R_SUCCESS) { - goto fail; + goto done; } /* * Add glue rdatasets. @@ -12534,6 +12945,7 @@ save_nsrrset(dns_message_t *message, dns_name_t *name, dns_db_t *db, result = dns_rdata_tostruct(&rdata, &ns, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); dns_rdata_reset(&rdata); + if (!dns_name_issubdomain(&ns.name, name)) { continue; } @@ -12543,41 +12955,92 @@ save_nsrrset(dns_message_t *message, dns_name_t *name, dns_db_t *db, dns_rdatatype_none, NULL, &rdataset); if (result == ISC_R_SUCCESS) { + has_glue = true; result = dns_db_findnode(db, &ns.name, true, &node); if (result != ISC_R_SUCCESS) { - goto fail; + goto done; } result = dns_db_addrdataset(db, node, version, 0, rdataset, 0, NULL); dns_db_detachnode(db, &node); if (result != ISC_R_SUCCESS) { - goto fail; + goto done; } } + rdataset = NULL; result = dns_message_findname( message, DNS_SECTION_ADDITIONAL, &ns.name, dns_rdatatype_a, dns_rdatatype_none, NULL, &rdataset); if (result == ISC_R_SUCCESS) { + has_glue = true; result = dns_db_findnode(db, &ns.name, true, &node); if (result != ISC_R_SUCCESS) { - goto fail; + goto done; } result = dns_db_addrdataset(db, node, version, 0, rdataset, 0, NULL); dns_db_detachnode(db, &node); if (result != ISC_R_SUCCESS) { - goto fail; + goto done; + } + } + + /* + * If no glue is found so far, we add the name to the list to + * resolve the A/AAAA glue later. If any glue is found in any + * iteration step, this list will be discarded and only the glue + * provided in this message will be used. + */ + if (!has_glue && dns_name_issubdomain(&ns.name, name)) { + dns_name_t *tmp_name; + tmp_name = isc_mem_get(cb_args->stub->mctx, + sizeof(*tmp_name)); + dns_name_init(tmp_name, NULL); + dns_name_dup(&ns.name, cb_args->stub->mctx, tmp_name); + ISC_LIST_APPEND(ns_list, tmp_name, link); + } + } + + if (result != ISC_R_NOMORE) { + goto done; + } + + /* + * If no glue records were found, we attempt to resolve A/AAAA + * for each NS entry found in the answer. + */ + if (!has_glue) { + for (ns_name = ISC_LIST_HEAD(ns_list); ns_name != NULL; + ns_name = ISC_LIST_NEXT(ns_name, link)) + { + /* + * Resolve NS IPv4 address/A. + */ + result = stub_request_nameserver_address(cb_args, true, + ns_name); + if (result != ISC_R_SUCCESS) { + goto done; + } + /* + * Resolve NS IPv6 address/AAAA. + */ + result = stub_request_nameserver_address(cb_args, false, + ns_name); + if (result != ISC_R_SUCCESS) { + goto done; } } } - if (result != ISC_R_NOMORE) { - goto fail; + + result = ISC_R_SUCCESS; + +done: + while ((ns_name = ISC_LIST_HEAD(ns_list)) != NULL) { + ISC_LIST_UNLINK(ns_list, ns_name, link); + dns_name_free(ns_name, cb_args->stub->mctx); + isc_mem_put(cb_args->stub->mctx, ns_name, sizeof(*ns_name)); } - - return (ISC_R_SUCCESS); - -fail: return (result); } @@ -12590,14 +13053,15 @@ stub_callback(isc_task_t *task, isc_event_t *event) { dns_zone_t *zone = NULL; char master[ISC_SOCKADDR_FORMATSIZE]; char source[ISC_SOCKADDR_FORMATSIZE]; - uint32_t nscnt, cnamecnt, refresh, retry, expire; + uint32_t nscnt, cnamecnt; isc_result_t result; isc_time_t now; bool exiting = false; - isc_interval_t i; - unsigned int j, soacount; + unsigned int j; + struct stub_cb_args *cb_args; - stub = revent->ev_arg; + cb_args = revent->ev_arg; + stub = cb_args->stub; INSIST(DNS_STUB_VALID(stub)); UNUSED(task); @@ -12725,10 +13189,13 @@ stub_callback(isc_task_t *task, isc_event_t *event) { goto next_master; } + atomic_fetch_add(&stub->pending_requests, 1); + /* * Save answer. */ - result = save_nsrrset(msg, &zone->origin, stub->db, stub->version); + result = save_nsrrset(msg, &zone->origin, cb_args, stub->db, + stub->version); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_INFO, "refreshing stub: unable to save NS records " @@ -12737,45 +13204,25 @@ stub_callback(isc_task_t *task, isc_event_t *event) { goto next_master; } - /* - * Tidy up. - */ - dns_db_closeversion(stub->db, &stub->version, true); - ZONEDB_LOCK(&zone->dblock, isc_rwlocktype_write); - if (zone->db == NULL) { - zone_attachdb(zone, stub->db); - } - result = zone_get_from_db(zone, zone->db, NULL, &soacount, NULL, - &refresh, &retry, &expire, NULL, NULL); - if (result == ISC_R_SUCCESS && soacount > 0U) { - zone->refresh = RANGE(refresh, zone->minrefresh, - zone->maxrefresh); - zone->retry = RANGE(retry, zone->minretry, zone->maxretry); - zone->expire = RANGE(expire, zone->refresh + zone->retry, - DNS_MAX_EXPIRE); - DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_HAVETIMERS); - } - ZONEDB_UNLOCK(&zone->dblock, isc_rwlocktype_write); - dns_db_detach(&stub->db); - dns_message_detach(&msg); isc_event_free(&event); dns_request_destroy(&zone->request); - DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_REFRESH); - DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED); - DNS_ZONE_JITTER_ADD(&now, zone->refresh, &zone->refreshtime); - isc_interval_set(&i, zone->expire, 0); - DNS_ZONE_TIME_ADD(&now, zone->expire, &zone->expiretime); - - if (zone->masterfile != NULL) { - zone_needdump(zone, 0); + /* + * Check to see if there are no outstanding requests and + * finish off if that is so. + */ + if (atomic_fetch_sub(&stub->pending_requests, 1) == 1) { + isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args)); + stub_finish_zone_update(stub, now); + goto free_stub; } - zone_settimer(zone, &now); - goto free_stub; + UNLOCK_ZONE(zone); + return; next_master: + isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args)); if (stub->version != NULL) { dns_db_closeversion(stub->db, &stub->version, false); } @@ -12836,6 +13283,7 @@ next_master: goto free_stub; same_master: + isc_mem_put(zone->mctx, cb_args, sizeof(*cb_args)); if (msg != NULL) { dns_message_detach(&msg); } @@ -13416,84 +13864,6 @@ queue_soa_query(dns_zone_t *zone) { } } -static inline isc_result_t -create_query(dns_zone_t *zone, dns_rdatatype_t rdtype, - dns_message_t **messagep) { - dns_message_t *message = NULL; - dns_name_t *qname = NULL; - dns_rdataset_t *qrdataset = NULL; - isc_result_t result; - - dns_message_create(zone->mctx, DNS_MESSAGE_INTENTRENDER, &message); - - message->opcode = dns_opcode_query; - message->rdclass = zone->rdclass; - - result = dns_message_gettempname(message, &qname); - if (result != ISC_R_SUCCESS) { - goto cleanup; - } - - result = dns_message_gettemprdataset(message, &qrdataset); - if (result != ISC_R_SUCCESS) { - goto cleanup; - } - - /* - * Make question. - */ - dns_name_init(qname, NULL); - dns_name_clone(&zone->origin, qname); - dns_rdataset_makequestion(qrdataset, zone->rdclass, rdtype); - ISC_LIST_APPEND(qname->list, qrdataset, link); - dns_message_addname(message, qname, DNS_SECTION_QUESTION); - - *messagep = message; - return (ISC_R_SUCCESS); - -cleanup: - if (qname != NULL) { - dns_message_puttempname(message, &qname); - } - if (qrdataset != NULL) { - dns_message_puttemprdataset(message, &qrdataset); - } - dns_message_detach(&message); - return (result); -} - -static isc_result_t -add_opt(dns_message_t *message, uint16_t udpsize, bool reqnsid, - bool reqexpire) { - isc_result_t result; - dns_rdataset_t *rdataset = NULL; - dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS]; - int count = 0; - - /* Set EDNS options if applicable */ - if (reqnsid) { - INSIST(count < DNS_EDNSOPTIONS); - ednsopts[count].code = DNS_OPT_NSID; - ednsopts[count].length = 0; - ednsopts[count].value = NULL; - count++; - } - if (reqexpire) { - INSIST(count < DNS_EDNSOPTIONS); - ednsopts[count].code = DNS_OPT_EXPIRE; - ednsopts[count].length = 0; - ednsopts[count].value = NULL; - count++; - } - result = dns_message_buildopt(message, &rdataset, 0, udpsize, 0, - ednsopts, count); - if (result != ISC_R_SUCCESS) { - return (result); - } - - return (dns_message_setopt(message, rdataset)); -} - static void soa_query(isc_task_t *task, isc_event_t *event) { const char me[] = "soa_query"; @@ -13528,7 +13898,7 @@ soa_query(isc_task_t *task, isc_event_t *event) { } again: - result = create_query(zone, dns_rdatatype_soa, &message); + result = create_query(zone, dns_rdatatype_soa, &zone->origin, &message); if (result != ISC_R_SUCCESS) { goto cleanup; } @@ -13729,6 +14099,7 @@ ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub) { bool reqnsid; uint16_t udpsize = SEND_BUFFER_SIZE; isc_dscp_t dscp = -1; + struct stub_cb_args *cb_args; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(LOCKED_ZONE(zone)); @@ -13745,6 +14116,7 @@ ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub) { stub->zone = NULL; stub->db = NULL; stub->version = NULL; + atomic_init(&stub->pending_requests, 0); /* * Attach so that the zone won't disappear from under us. @@ -13815,7 +14187,7 @@ ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub) { /* * XXX Optimisation: Create message when zone is setup and reuse. */ - result = create_query(zone, dns_rdatatype_ns, &message); + result = create_query(zone, dns_rdatatype_ns, &zone->origin, &message); INSIST(result == ISC_R_SUCCESS); INSIST(zone->masterscnt > 0); @@ -13920,10 +14292,21 @@ ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub) { if (DNS_ZONE_FLAG(zone, DNS_ZONEFLG_DIALREFRESH)) { timeout = 30; } + + /* Save request parameters so we can reuse them later on + for resolving missing glue A/AAAA records. */ + cb_args = isc_mem_get(zone->mctx, sizeof(*cb_args)); + cb_args->stub = stub; + cb_args->tsig_key = key; + cb_args->dscp = dscp; + cb_args->udpsize = udpsize; + cb_args->timeout = timeout; + cb_args->reqnsid = reqnsid; + result = dns_request_createvia( zone->view->requestmgr, message, &zone->sourceaddr, &zone->masteraddr, dscp, DNS_REQUESTOPT_TCP, key, timeout * 3, - timeout, 0, zone->task, stub_callback, stub, &zone->request); + timeout, 0, zone->task, stub_callback, cb_args, &zone->request); if (result != ISC_R_SUCCESS) { zone_debuglog(zone, me, 1, "dns_request_createvia() failed: %s", dns_result_totext(result)); From d727eaae6c75fa5c8d66e035e8525dcbd2b50b77 Mon Sep 17 00:00:00 2001 From: Diego Fronza Date: Wed, 30 Sep 2020 17:22:39 -0300 Subject: [PATCH 2/5] Always return address records in additional section for NS queries --- lib/ns/query.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/ns/query.c b/lib/ns/query.c index 91b1bd36fa..fd4c7fb9c7 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -1683,7 +1683,9 @@ query_additional_cb(void *arg, const dns_name_t *name, dns_rdatatype_t qtype) { * If we want only minimal responses and are here, then it must * be for glue. */ - if (qctx->view->minimalresponses == dns_minimal_yes) { + if (qctx->view->minimalresponses == dns_minimal_yes && + client->query.qtype != dns_rdatatype_ns) + { goto try_glue; } @@ -11287,6 +11289,12 @@ ns_query_start(ns_client_t *client, isc_nmhandle_t *handle) { { client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY | NS_QUERYATTR_NOADDITIONAL); + } else if (qtype == dns_rdatatype_ns) { + /* + * Always turn on additional records for NS queries. + */ + client->query.attributes &= ~(NS_QUERYATTR_NOAUTHORITY | + NS_QUERYATTR_NOADDITIONAL); } /* From b0f61f92b3b4a1c973b424b442b9d590c203d6d8 Mon Sep 17 00:00:00 2001 From: Diego Fronza Date: Thu, 10 Sep 2020 15:33:15 -0300 Subject: [PATCH 3/5] Added test for the proposed fix This test is very simple, two nameserver instances are created: - ns4: master, with 'minimal-responses yes', authoritative for example. zone - ns5: slave, stub zone The first thing verified is the transfer of zone data from master to slave, which should be saved in ns5/example.db. After that, a query is issued to ns5 asking for target.example. TXT, a record present in the master database with the "test" string as content. If that query works, it means stub zone successfully request nameserver addresses from master, ns4.example. A/AAAA The presence of both A/AAAA records for ns4 is also verified in the stub zone local file, ns5/example.db. --- bin/tests/system/stub/clean.sh | 3 ++- bin/tests/system/stub/ns4/example.db | 21 ++++++++++++++++ bin/tests/system/stub/ns4/named.conf.in | 29 ++++++++++++++++++++++ bin/tests/system/stub/ns5/named.conf.in | 32 +++++++++++++++++++++++++ bin/tests/system/stub/setup.sh | 2 ++ bin/tests/system/stub/tests.sh | 21 ++++++++++++++++ 6 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 bin/tests/system/stub/ns4/example.db create mode 100644 bin/tests/system/stub/ns4/named.conf.in create mode 100644 bin/tests/system/stub/ns5/named.conf.in diff --git a/bin/tests/system/stub/clean.sh b/bin/tests/system/stub/clean.sh index 7081eaa509..56ef8e2435 100644 --- a/bin/tests/system/stub/clean.sh +++ b/bin/tests/system/stub/clean.sh @@ -12,9 +12,10 @@ # # Clean up after stub tests. # -rm -f dig.out.ns3 ns3/child.example.st +rm -f dig.out.ns[35] ns3/child.example.st rm -f */named.memstats rm -f */named.conf rm -f */named.run rm -f ns*/named.lock rm -f ns*/managed-keys.bind* +rm -f ns5/example.db diff --git a/bin/tests/system/stub/ns4/example.db b/bin/tests/system/stub/ns4/example.db new file mode 100644 index 0000000000..06f352b252 --- /dev/null +++ b/bin/tests/system/stub/ns4/example.db @@ -0,0 +1,21 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 ; 5 minutes +@ IN SOA ns4.example. hostmaster.example. ( + 2000042795 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +@ IN NS ns4 +ns4 IN A 10.53.0.4 + IN AAAA fd92:7065:b8e:ffff::4 +target IN TXT "test" diff --git a/bin/tests/system/stub/ns4/named.conf.in b/bin/tests/system/stub/ns4/named.conf.in new file mode 100644 index 0000000000..5c44380e2c --- /dev/null +++ b/bin/tests/system/stub/ns4/named.conf.in @@ -0,0 +1,29 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.4; + notify-source 10.53.0.4; + transfer-source 10.53.0.4; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.4; }; + listen-on-v6 { none; }; + recursion no; + notify yes; + minimal-responses yes; + dnssec-validation no; +}; + +zone "example" { + type primary; + file "example.db"; +}; diff --git a/bin/tests/system/stub/ns5/named.conf.in b/bin/tests/system/stub/ns5/named.conf.in new file mode 100644 index 0000000000..5e5a1ac40a --- /dev/null +++ b/bin/tests/system/stub/ns5/named.conf.in @@ -0,0 +1,32 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + query-source address 10.53.0.5; + notify-source 10.53.0.5; + transfer-source 10.53.0.5; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.5; }; + listen-on-v6 { none; }; + dnssec-validation no; +}; + +zone "." { + type hint; + file "../../common/root.hint"; +}; + +zone "example" { + type stub; + file "example.db"; + masters { 10.53.0.4 port @PORT@; }; +}; diff --git a/bin/tests/system/stub/setup.sh b/bin/tests/system/stub/setup.sh index c4670066e3..ad34e50400 100644 --- a/bin/tests/system/stub/setup.sh +++ b/bin/tests/system/stub/setup.sh @@ -14,3 +14,5 @@ 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 +copy_setports ns4/named.conf.in ns4/named.conf +copy_setports ns5/named.conf.in ns5/named.conf diff --git a/bin/tests/system/stub/tests.sh b/bin/tests/system/stub/tests.sh index 456e4ed934..09a1993420 100644 --- a/bin/tests/system/stub/tests.sh +++ b/bin/tests/system/stub/tests.sh @@ -59,5 +59,26 @@ digcomp knowngood.dig.out.rec dig.out.ns3 || ret=1 } done +echo_i "check that glue record is correctly transferred from master when minimal-responses is on" +ret=0 +# First ensure that zone data was transfered. +for i in 1 2 3 4 5 6 7; do + [ -f ns5/example.db ] && break + sleep 1 +done + +if [ -f ns5/example.db ]; then + # If NS glue wasn't transferred, this query would fail. + $DIG $DIGOPTS +nodnssec @10.53.0.5 target.example. txt > dig.out.ns5 || ret=1 + grep 'target\.example.*TXT.*"test"' dig.out.ns5 > /dev/null || ret=1 + # Ensure both ipv4 and ipv6 glue records were transferred. + grep -E 'ns4[[:space:]]+A[[:space:]]+10.53.0.4' ns5/example.db > /dev/null || ret=1 + grep -E 'AAAA[[:space:]]+fd92:7065:b8e:ffff::4' ns5/example.db > /dev/null || ret=1 + [ $ret = 0 ] || { status=1; echo_i "failed"; } +else + status=1 + echo_i "failed: stub zone transfer failed ns4(master) <---> ns5/example.db" +fi + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 From 8f5545fa0bf69e944e2584aaf67152f7793ff7cd Mon Sep 17 00:00:00 2001 From: Diego Fronza Date: Fri, 2 Oct 2020 12:07:59 -0300 Subject: [PATCH 4/5] Adjusted additional system test (NS, non-root zone) After the updates from this branch, BIND now sends glue records for NS queries even when configured with minimal-responses yes. --- bin/tests/system/additional/tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/tests/system/additional/tests.sh b/bin/tests/system/additional/tests.sh index c7e6b4dd9f..dedfd018a9 100644 --- a/bin/tests/system/additional/tests.sh +++ b/bin/tests/system/additional/tests.sh @@ -226,7 +226,7 @@ dotests() { $DIG $DIGOPTS -t NS rt.example @10.53.0.1 > dig.out.$n || ret=1 case $minimal in yes) - grep 'ADDITIONAL: 1' dig.out.$n > /dev/null || ret=1 + grep 'ADDITIONAL: 2' dig.out.$n > /dev/null || ret=1 ;; no) grep 'ADDITIONAL: 2' dig.out.$n > /dev/null || ret=1 From 6026cea10c3dd18ea1bbc19d6443adc86d5cc680 Mon Sep 17 00:00:00 2001 From: Diego Fronza Date: Thu, 10 Sep 2020 15:51:46 -0300 Subject: [PATCH 5/5] Add CHANGES entry --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 7de204397a..e089b2260d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +5518. [bug] Fix stub zone not transferring nameserver addresses + from masters configured with 'minimal-responses yes'. + [GL #1736] + 5517. [bug] Handle 'UV_EOF' differently and don't contribute it to the RECVFAIL statistic count. [GL #2208]