2b68233
From 61596a9aac5f0d4cef3845b04d61f2dad4aa0814 Mon Sep 17 00:00:00 2001
2b68233
From: Lennart Poettering <lennart@poettering.net>
2b68233
Date: Mon, 22 Feb 2016 20:39:45 +0100
2b68233
Subject: [PATCH] resolved: fix notification iteration logic when transactions
2b68233
 are completed
2b68233
2b68233
When a transaction is complete, and we notify its owners, make sure we deal
2b68233
correctly with the requesters removing themselves from the list of owners while
2b68233
we continue iterating.
2b68233
2b68233
This was previously already dealt with with transactions that require other
2b68233
transactions for DNSSEC purposes, fix this for other possibly transaction
2b68233
owners too now.
2b68233
2b68233
Since iterating through "Set" objects is not safe regarding removal of entries
2b68233
from it, rework the logic to use two Sets, and move each entry we notified from
2b68233
one set to the other set before we dispatch the notification. This move operation
2b68233
requires no additional memory, and enables us to ensure that we don't notify
2b68233
any object twice.
2b68233
2b68233
Fixes: #2676
2b68233
(cherry picked from commit 35aa04e9edf422beac3493afa555d29575b3046c)
2b68233
---
2b68233
 src/basic/macro.h                      |  6 ++++
2b68233
 src/basic/set.h                        |  3 ++
2b68233
 src/resolve/resolved-dns-query.c       |  5 +++
2b68233
 src/resolve/resolved-dns-transaction.c | 62 ++++++++++++++++------------------
2b68233
 src/resolve/resolved-dns-transaction.h |  6 ++--
2b68233
 src/resolve/resolved-dns-zone.c        |  5 +++
2b68233
 6 files changed, 52 insertions(+), 35 deletions(-)
2b68233
2b68233
diff --git a/src/basic/macro.h b/src/basic/macro.h
2b68233
index 2695d0edb7..ab5cc97e17 100644
2b68233
--- a/src/basic/macro.h
2b68233
+++ b/src/basic/macro.h
2b68233
@@ -361,6 +361,12 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
2b68233
                 _found;                         \
2b68233
         })
2b68233
 
2b68233
+#define SWAP_TWO(x, y) do {                        \
2b68233
+                typeof(x) _t = (x);                \
2b68233
+                (x) = (y);                         \
2b68233
+                (y) = (_t);                        \
2b68233
+        } while (false)
2b68233
+
2b68233
 /* Define C11 thread_local attribute even on older gcc compiler
2b68233
  * version */
2b68233
 #ifndef thread_local
2b68233
diff --git a/src/basic/set.h b/src/basic/set.h
2b68233
index 2bff5062da..e0d9dd001c 100644
2b68233
--- a/src/basic/set.h
2b68233
+++ b/src/basic/set.h
2b68233
@@ -126,6 +126,9 @@ int set_put_strdupv(Set *s, char **l);
2b68233
 #define SET_FOREACH(e, s, i) \
2b68233
         for ((i) = ITERATOR_FIRST; set_iterate((s), &(i), (void**)&(e)); )
2b68233
 
2b68233
+#define SET_FOREACH_MOVE(e, d, s)                                       \
2b68233
+        for (; ({ e = set_first(s); assert_se(!e || set_move_one(d, s, e) >= 0); e; }); )
2b68233
+
2b68233
 DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free);
2b68233
 DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free);
2b68233
 
2b68233
diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c
2b68233
index a378b2b7f7..2a02544eb6 100644
2b68233
--- a/src/resolve/resolved-dns-query.c
2b68233
+++ b/src/resolve/resolved-dns-query.c
2b68233
@@ -62,6 +62,7 @@ static void dns_query_candidate_stop(DnsQueryCandidate *c) {
2b68233
 
2b68233
         while ((t = set_steal_first(c->transactions))) {
2b68233
                 set_remove(t->notify_query_candidates, c);
2b68233
+                set_remove(t->notify_query_candidates_done, c);
2b68233
                 dns_transaction_gc(t);
2b68233
         }
2b68233
 }
2b68233
@@ -139,6 +140,10 @@ static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResource
2b68233
         if (r < 0)
2b68233
                 goto gc;
2b68233
 
2b68233
+        r = set_ensure_allocated(&t->notify_query_candidates_done, NULL);
2b68233
+        if (r < 0)
2b68233
+                goto gc;
2b68233
+
2b68233
         r = set_put(t->notify_query_candidates, c);
2b68233
         if (r < 0)
2b68233
                 goto gc;
2b68233
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
2b68233
index d48fdd1281..4f5cbab702 100644
2b68233
--- a/src/resolve/resolved-dns-transaction.c
2b68233
+++ b/src/resolve/resolved-dns-transaction.c
2b68233
@@ -52,6 +52,7 @@ static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) {
2b68233
 
2b68233
         while ((z = set_steal_first(t->dnssec_transactions))) {
2b68233
                 set_remove(z->notify_transactions, t);
2b68233
+                set_remove(z->notify_transactions_done, t);
2b68233
                 dns_transaction_gc(z);
2b68233
         }
2b68233
 }
2b68233
@@ -100,14 +101,26 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) {
2b68233
                 set_remove(c->transactions, t);
2b68233
         set_free(t->notify_query_candidates);
2b68233
 
2b68233
+        while ((c = set_steal_first(t->notify_query_candidates_done)))
2b68233
+                set_remove(c->transactions, t);
2b68233
+        set_free(t->notify_query_candidates_done);
2b68233
+
2b68233
         while ((i = set_steal_first(t->notify_zone_items)))
2b68233
                 i->probe_transaction = NULL;
2b68233
         set_free(t->notify_zone_items);
2b68233
 
2b68233
+        while ((i = set_steal_first(t->notify_zone_items_done)))
2b68233
+                i->probe_transaction = NULL;
2b68233
+        set_free(t->notify_zone_items_done);
2b68233
+
2b68233
         while ((z = set_steal_first(t->notify_transactions)))
2b68233
                 set_remove(z->dnssec_transactions, t);
2b68233
         set_free(t->notify_transactions);
2b68233
 
2b68233
+        while ((z = set_steal_first(t->notify_transactions_done)))
2b68233
+                set_remove(z->dnssec_transactions, t);
2b68233
+        set_free(t->notify_transactions_done);
2b68233
+
2b68233
         dns_transaction_flush_dnssec_transactions(t);
2b68233
         set_free(t->dnssec_transactions);
2b68233
 
2b68233
@@ -128,8 +141,11 @@ bool dns_transaction_gc(DnsTransaction *t) {
2b68233
                 return true;
2b68233
 
2b68233
         if (set_isempty(t->notify_query_candidates) &&
2b68233
+            set_isempty(t->notify_query_candidates_done) &&
2b68233
             set_isempty(t->notify_zone_items) &&
2b68233
-            set_isempty(t->notify_transactions)) {
2b68233
+            set_isempty(t->notify_zone_items_done) &&
2b68233
+            set_isempty(t->notify_transactions) &&
2b68233
+            set_isempty(t->notify_transactions_done)) {
2b68233
                 dns_transaction_free(t);
2b68233
                 return false;
2b68233
         }
2b68233
@@ -266,6 +282,7 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) {
2b68233
         log_debug("We have the lexicographically larger IP address and thus lost in the conflict.");
2b68233
 
2b68233
         t->block_gc++;
2b68233
+
2b68233
         while ((z = set_first(t->notify_zone_items))) {
2b68233
                 /* First, make sure the zone item drops the reference
2b68233
                  * to us */
2b68233
@@ -284,7 +301,6 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
2b68233
         DnsQueryCandidate *c;
2b68233
         DnsZoneItem *z;
2b68233
         DnsTransaction *d;
2b68233
-        Iterator i;
2b68233
         const char *st;
2b68233
 
2b68233
         assert(t);
2b68233
@@ -329,39 +345,17 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
2b68233
          * transaction isn't freed while we are still looking at it */
2b68233
         t->block_gc++;
2b68233
 
2b68233
-        SET_FOREACH(c, t->notify_query_candidates, i)
2b68233
+        SET_FOREACH_MOVE(c, t->notify_query_candidates_done, t->notify_query_candidates)
2b68233
                 dns_query_candidate_notify(c);
2b68233
-        SET_FOREACH(z, t->notify_zone_items, i)
2b68233
-                dns_zone_item_notify(z);
2b68233
+        SWAP_TWO(t->notify_query_candidates, t->notify_query_candidates_done);
2b68233
 
2b68233
-        if (!set_isempty(t->notify_transactions)) {
2b68233
-                DnsTransaction **nt;
2b68233
-                unsigned j, n = 0;
2b68233
-
2b68233
-                /* We need to be careful when notifying other
2b68233
-                 * transactions, as that might destroy other
2b68233
-                 * transactions in our list. Hence, in order to be
2b68233
-                 * able to safely iterate through the list of
2b68233
-                 * transactions, take a GC lock on all of them
2b68233
-                 * first. Then, in a second loop, notify them, but
2b68233
-                 * first unlock that specific transaction. */
2b68233
-
2b68233
-                nt = newa(DnsTransaction*, set_size(t->notify_transactions));
2b68233
-                SET_FOREACH(d, t->notify_transactions, i) {
2b68233
-                        nt[n++] = d;
2b68233
-                        d->block_gc++;
2b68233
-                }
2b68233
-
2b68233
-                assert(n == set_size(t->notify_transactions));
2b68233
+        SET_FOREACH_MOVE(z, t->notify_zone_items_done, t->notify_zone_items)
2b68233
+                dns_zone_item_notify(z);
2b68233
+        SWAP_TWO(t->notify_zone_items, t->notify_zone_items_done);
2b68233
 
2b68233
-                for (j = 0; j < n; j++) {
2b68233
-                        if (set_contains(t->notify_transactions, nt[j]))
2b68233
-                                dns_transaction_notify(nt[j], t);
2b68233
-
2b68233
-                        nt[j]->block_gc--;
2b68233
-                        dns_transaction_gc(nt[j]);
2b68233
-                }
2b68233
-        }
2b68233
+        SET_FOREACH_MOVE(d, t->notify_transactions_done, t->notify_transactions)
2b68233
+                dns_transaction_notify(d, t);
2b68233
+        SWAP_TWO(t->notify_transactions, t->notify_transactions_done);
2b68233
 
2b68233
         t->block_gc--;
2b68233
         dns_transaction_gc(t);
2b68233
@@ -1619,6 +1613,10 @@ static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResource
2b68233
         if (r < 0)
2b68233
                 goto gc;
2b68233
 
2b68233
+        r = set_ensure_allocated(&aux->notify_transactions_done, NULL);
2b68233
+        if (r < 0)
2b68233
+                goto gc;
2b68233
+
2b68233
         r = set_put(t->dnssec_transactions, aux);
2b68233
         if (r < 0)
2b68233
                 goto gc;
2b68233
diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h
2b68233
index 4617194711..fd0237d166 100644
2b68233
--- a/src/resolve/resolved-dns-transaction.h
2b68233
+++ b/src/resolve/resolved-dns-transaction.h
2b68233
@@ -119,17 +119,17 @@ struct DnsTransaction {
2b68233
         /* Query candidates this transaction is referenced by and that
2b68233
          * shall be notified about this specific transaction
2b68233
          * completing. */
2b68233
-        Set *notify_query_candidates;
2b68233
+        Set *notify_query_candidates, *notify_query_candidates_done;
2b68233
 
2b68233
         /* Zone items this transaction is referenced by and that shall
2b68233
          * be notified about completion. */
2b68233
-        Set *notify_zone_items;
2b68233
+        Set *notify_zone_items, *notify_zone_items_done;
2b68233
 
2b68233
         /* Other transactions that this transactions is referenced by
2b68233
          * and that shall be notified about completion. This is used
2b68233
          * when transactions want to validate their RRsets, but need
2b68233
          * another DNSKEY or DS RR to do so. */
2b68233
-        Set *notify_transactions;
2b68233
+        Set *notify_transactions, *notify_transactions_done;
2b68233
 
2b68233
         /* The opposite direction: the transactions this transaction
2b68233
          * created in order to request DNSKEY or DS RRs. */
2b68233
diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c
2b68233
index f52383cfd1..be535cff14 100644
2b68233
--- a/src/resolve/resolved-dns-zone.c
2b68233
+++ b/src/resolve/resolved-dns-zone.c
2b68233
@@ -38,6 +38,7 @@ void dns_zone_item_probe_stop(DnsZoneItem *i) {
2b68233
         i->probe_transaction = NULL;
2b68233
 
2b68233
         set_remove(t->notify_zone_items, i);
2b68233
+        set_remove(t->notify_zone_items_done, i);
2b68233
         dns_transaction_gc(t);
2b68233
 }
2b68233
 
2b68233
@@ -186,6 +187,10 @@ static int dns_zone_item_probe_start(DnsZoneItem *i)  {
2b68233
         if (r < 0)
2b68233
                 goto gc;
2b68233
 
2b68233
+        r = set_ensure_allocated(&t->notify_zone_items_done, NULL);
2b68233
+        if (r < 0)
2b68233
+                goto gc;
2b68233
+
2b68233
         r = set_put(t->notify_zone_items, i);
2b68233
         if (r < 0)
2b68233
                 goto gc;