From ec2098ca35039e4f81fd0aa7c525eb960b8f47bf Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Mon, 14 Nov 2022 12:18:06 +0000 Subject: [PATCH] Cancel all fetch events in dns_resolver_cancelfetch() Although 'dns_fetch_t' fetch can have two associated events, one for each of 'DNS_EVENT_FETCHDONE' and 'DNS_EVENT_TRYSTALE' types, the dns_resolver_cancelfetch() function is designed in a way that it expects only one existing event, which it must cancel, and when it happens so that 'stale-answer-client-timeout' is enabled and there are two events, only one of them is canceled, and it results in an assertion in dns_resolver_destroyfetch(), when it finds a dangling event. Change the logic of dns_resolver_cancelfetch() function so that it cancels both the events (if they exist), and in the right order. --- lib/dns/resolver.c | 65 +++++++++++++++++++++++++++++++++++++++------- lib/ns/query.c | 4 ++- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 973337f2ac..411b0af6d4 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -10701,6 +10701,8 @@ fail: void dns_resolver_cancelfetch(dns_fetch_t *fetch) { fetchctx_t *fctx = NULL; + dns_fetchevent_t *event_trystale = NULL; + dns_fetchevent_t *event_fetchdone = NULL; REQUIRE(DNS_FETCH_VALID(fetch)); fctx = fetch->private; @@ -10711,24 +10713,69 @@ dns_resolver_cancelfetch(dns_fetch_t *fetch) { LOCK(&fctx->lock); /* - * Find the completion event for this fetch (as opposed - * to those for other fetches that have joined the same - * fctx) and send it with result = ISC_R_CANCELED. + * Find all the events associated with the provided 'fetch' (as opposed + * to those for other fetches that have joined the same fctx). The + * event(s) found are only sent and removed from the fctx->events list + * after this loop is finished (i.e. the fctx->events list is not + * modified during iteration). */ for (dns_fetchevent_t *event = ISC_LIST_HEAD(fctx->events); event != NULL; event = ISC_LIST_NEXT(event, ev_link)) { - if (event->fetch == fetch) { - isc_task_t *task = event->ev_sender; - ISC_LIST_UNLINK(fctx->events, event, ev_link); - event->ev_sender = fctx; - event->result = ISC_R_CANCELED; + /* + * Only process events associated with the provided 'fetch'. + */ + if (event->fetch != fetch) { + continue; + } - isc_task_sendanddetach(&task, ISC_EVENT_PTR(&event)); + /* + * Track various events associated with the provided 'fetch' in + * separate variables as they will need to be sent in a + * specific order. + */ + switch (event->ev_type) { + case DNS_EVENT_TRYSTALE: + INSIST(event_trystale == NULL); + event_trystale = event; + break; + case DNS_EVENT_FETCHDONE: + INSIST(event_fetchdone == NULL); + event_fetchdone = event; + break; + default: + UNREACHABLE(); + } + + /* + * Break out of the loop once all possible types of events + * associated with the provided 'fetch' are found. + */ + if (event_trystale != NULL && event_fetchdone != NULL) { break; } } + /* + * The "trystale" event must be sent before the "fetchdone" event, + * because the latter clears the "recursing" query attribute, which is + * required by both events (handled by the same callback function). + */ + if (event_trystale != NULL) { + isc_task_t *etask = event_trystale->ev_sender; + ISC_LIST_UNLINK(fctx->events, event_trystale, ev_link); + event_trystale->ev_sender = fctx; + event_trystale->result = ISC_R_CANCELED; + isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_trystale)); + } + if (event_fetchdone != NULL) { + isc_task_t *etask = event_fetchdone->ev_sender; + ISC_LIST_UNLINK(fctx->events, event_fetchdone, ev_link); + event_fetchdone->ev_sender = fctx; + event_fetchdone->result = ISC_R_CANCELED; + isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_fetchdone)); + } + /* * The fctx continues running even if no fetches remain; * the answer is still cached. diff --git a/lib/ns/query.c b/lib/ns/query.c index d2dcafe683..eddbcd98ac 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -6257,7 +6257,9 @@ fetch_callback(isc_task_t *task, isc_event_t *event) { CTRACE(ISC_LOG_DEBUG(3), "fetch_callback"); if (event->ev_type == DNS_EVENT_TRYSTALE) { - query_lookup_stale(client); + if (devent->result != ISC_R_CANCELED) { + query_lookup_stale(client); + } isc_event_free(ISC_EVENT_PTR(&event)); return; }