]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Fix more ns_statscounter_recursclients underflows
authorMichał Kępień <michal@isc.org>
Wed, 23 Feb 2022 13:39:11 +0000 (14:39 +0100)
committerMichał Kępień <michal@isc.org>
Wed, 23 Feb 2022 13:45:06 +0000 (14:45 +0100)
Commit aab691d51266f552a7923db32686fb9398b1d255 did not fix all possible
scenarios in which the ns_statscounter_recursclients counter underflows.
The solution implemented therein can be ineffective e.g. when CNAME
chaining happens with prefetching enabled.

Here is an example recursive resolution scenario in which the
ns_statscounter_recursclients counter can underflow with the current
logic in effect:

 1. Query processing starts, the answer is not found in the cache, so
    recursion is started.  The NS_CLIENTATTR_RECURSING attribute is set.
    ns_statscounter_recursclients is incremented (Δ = +1).

 2. Recursion completes, returning a CNAME.  client->recursionquota is
    non-NULL, so the NS_CLIENTATTR_RECURSING attribute remains set.
    ns_statscounter_recursclients is decremented (Δ = 0).

 3. Query processing restarts.

 4. The current QNAME (the target of the CNAME from step 2) is found in
    the cache, with a TTL low enough to trigger a prefetch.

 5. query_prefetch() attaches to client->recursionquota.
    ns_statscounter_recursclients is not incremented because
    query_prefetch() does not do that (Δ = 0).

 6. Query processing restarts.

 7. The current QNAME (the target of the CNAME from step 4) is not found
    in the cache, so recursion is started.  client->recursionquota is
    already attached to (since step 5) and the NS_CLIENTATTR_RECURSING
    attribute is set (since step 1), so ns_statscounter_recursclients is
    not incremented (Δ = 0).

 8. The prefetch from step 5 completes.  client->recursionquota is
    detached from in prefetch_done().  ns_statscounter_recursclients is
    not decremented because prefetch_done() does not do that (Δ = 0).

 9. Recursion for the current QNAME completes.  client->recursionquota
    is already detached from, i.e. set to NULL (since step 8), and the
    NS_CLIENTATTR_RECURSING attribute is set (since step 1), so
    ns_statscounter_recursclients is decremented (Δ = -1).

Another possible scenario is that after step 7, recursion for the target
of the CNAME from step 4 completes before the prefetch for the CNAME
itself.  fetch_callback() then notices that client->recursionquota is
non-NULL and decrements ns_statscounter_recursclients, even though
client->recursionquota was attached to by query_prefetch() and therefore
not accompanied by an incrementation of ns_statscounter_recursclients.
The net result is also an underflow.

Instead of trying to properly handle all possible orderings of events
set into motion by normal recursion and prefetch-triggered recursion,
adjust ns_statscounter_recursclients whenever the recursive clients
quota is successfully attached to or detached from.  Remove the
NS_CLIENTATTR_RECURSING attribute altogether as its only purpose is made
obsolete by this change.

(cherry picked from commit f7482b68b9623cad01f21fc8816d84a29183f2d1)

lib/ns/include/ns/client.h
lib/ns/query.c

index 1fd08633d30b823a4717d999dc4f3fa781880b03..91e8ccf8dcf70155f7b54a1a19913351da992be5 100644 (file)
@@ -270,8 +270,7 @@ struct ns_client {
 #define NS_CLIENTATTR_WANTPAD     0x08000 /*%< pad reply */
 #define NS_CLIENTATTR_USEKEEPALIVE 0x10000 /*%< use TCP keepalive */
 
-#define NS_CLIENTATTR_NOSETFC  0x20000 /*%< don't set servfail cache */
-#define NS_CLIENTATTR_RECURSING 0x40000 /*%< client is recursing */
+#define NS_CLIENTATTR_NOSETFC 0x20000 /*%< don't set servfail cache */
 
 /*
  * Flag to use with the SERVFAIL cache to indicate
index 335d877d489bbffd7b8f0d625b53c8b5f0b6c409..176c552b41da257d61a7bd775715b598bcb65ca6 100644 (file)
@@ -2486,6 +2486,8 @@ prefetch_done(isc_task_t *task, isc_event_t *event) {
         */
        if (client->recursionquota != NULL) {
                isc_quota_detach(&client->recursionquota);
+               ns_stats_decrement(client->sctx->nsstats,
+                                  ns_statscounter_recursclients);
        }
 
        free_devent(client, &event, &devent);
@@ -2513,10 +2515,15 @@ query_prefetch(ns_client_t *client, dns_name_t *qname,
        if (client->recursionquota == NULL) {
                result = isc_quota_attach(&client->sctx->recursionquota,
                                          &client->recursionquota);
-               if (result == ISC_R_SOFTQUOTA) {
+               switch (result) {
+               case ISC_R_SUCCESS:
+                       ns_stats_increment(client->sctx->nsstats,
+                                          ns_statscounter_recursclients);
+                       break;
+               case ISC_R_SOFTQUOTA:
                        isc_quota_detach(&client->recursionquota);
-               }
-               if (result != ISC_R_SUCCESS) {
+                       /* FALLTHROUGH */
+               default:
                        return;
                }
        }
@@ -2726,10 +2733,15 @@ query_rpzfetch(ns_client_t *client, dns_name_t *qname, dns_rdatatype_t type) {
        if (client->recursionquota == NULL) {
                result = isc_quota_attach(&client->sctx->recursionquota,
                                          &client->recursionquota);
-               if (result == ISC_R_SOFTQUOTA) {
+               switch (result) {
+               case ISC_R_SUCCESS:
+                       ns_stats_increment(client->sctx->nsstats,
+                                          ns_statscounter_recursclients);
+                       break;
+               case ISC_R_SOFTQUOTA:
                        isc_quota_detach(&client->recursionquota);
-               }
-               if (result != ISC_R_SUCCESS) {
+                       /* FALLTHROUGH */
+               default:
                        return;
                }
        }
@@ -6094,15 +6106,6 @@ fetch_callback(isc_task_t *task, isc_event_t *event) {
                isc_quota_detach(&client->recursionquota);
                ns_stats_decrement(client->sctx->nsstats,
                                   ns_statscounter_recursclients);
-       } else if (client->attributes & NS_CLIENTATTR_RECURSING) {
-               client->attributes &= ~NS_CLIENTATTR_RECURSING;
-               /*
-                * Detached from recursionquota via prefetch_done(),
-                * but need to decrement recursive client stats here anyway,
-                * since it was incremented in ns_query_recurse().
-                */
-               ns_stats_decrement(client->sctx->nsstats,
-                                  ns_statscounter_recursclients);
        }
 
        LOCK(&client->manager->reclock);
@@ -6268,7 +6271,6 @@ ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
                if (result == ISC_R_SUCCESS || result == ISC_R_SOFTQUOTA) {
                        ns_stats_increment(client->sctx->nsstats,
                                           ns_statscounter_recursclients);
-                       client->attributes |= NS_CLIENTATTR_RECURSING;
                }
 
                if (result == ISC_R_SOFTQUOTA) {
@@ -6323,18 +6325,6 @@ ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
 
                dns_message_clonebuffer(client->message);
                ns_client_recursing(client);
-       } else if ((client->attributes & NS_CLIENTATTR_RECURSING) == 0) {
-               client->attributes |= NS_CLIENTATTR_RECURSING;
-               /*
-                * query_prefetch() attached first to client->recursionquota,
-                * but we must check if NS_CLIENTATTR_RECURSING attribute is
-                * on, if not then turn it on and increment recursing clients
-                * stats counter only once. The attribute is also checked in
-                * fetch_callback() to know if a matching decrement to this
-                * counter should be applied.
-                */
-               ns_stats_increment(client->sctx->nsstats,
-                                  ns_statscounter_recursclients);
        }
 
        /*