}
static void
-dns__acl_destroy(dns_acl_t *dacl) {
- unsigned int i;
- dns_acl_port_transports_t *port_proto;
+dns__acl_destroy_port_transports(dns_acl_t *acl) {
+ dns_acl_port_transports_t *port_proto = NULL;
+ dns_acl_port_transports_t *next = NULL;
+ ISC_LIST_FOREACH_SAFE (acl->ports_and_transports, port_proto, link,
+ next)
+ {
+ ISC_LIST_DEQUEUE(acl->ports_and_transports, port_proto, link);
+ isc_mem_put(acl->mctx, port_proto, sizeof(*port_proto));
+ }
+}
+static void
+dns__acl_destroy(dns_acl_t *dacl) {
INSIST(!ISC_LINK_LINKED(dacl, nextincache));
- for (i = 0; i < dacl->length; i++) {
+ isc_refcount_destroy(&dacl->references);
+ dacl->magic = 0;
+
+ for (size_t i = 0; i < dacl->length; i++) {
dns_aclelement_t *de = &dacl->elements[i];
if (de->type == dns_aclelementtype_keyname) {
dns_name_free(&de->keyname, dacl->mctx);
dns_iptable_detach(&dacl->iptable);
}
- port_proto = ISC_LIST_HEAD(dacl->ports_and_transports);
- while (port_proto != NULL) {
- dns_acl_port_transports_t *next = NULL;
-
- next = ISC_LIST_NEXT(port_proto, link);
- ISC_LIST_DEQUEUE(dacl->ports_and_transports, port_proto, link);
- isc_mem_put(dacl->mctx, port_proto, sizeof(*port_proto));
- port_proto = next;
- }
+ dns__acl_destroy_port_transports(dacl);
- isc_refcount_destroy(&dacl->references);
- dacl->magic = 0;
isc_mem_putanddetach(&dacl->mctx, dacl, sizeof(*dacl));
}
REQUIRE(DNS_ACL_VALID(localhost));
REQUIRE(DNS_ACL_VALID(localnets));
- rcu_read_lock();
localhost = rcu_xchg_pointer(&env->localhost, dns_acl_ref(localhost));
localnets = rcu_xchg_pointer(&env->localnets, dns_acl_ref(localnets));
- rcu_read_unlock();
+
+ /*
+ * This function is called only during interface scanning, so blocking
+ * a bit is acceptable. Wait until all ongoing attachments to old
+ * 'localhost' and 'localnets' are finished before we can detach and
+ * possibly destroy them.
+ *
+ * The problem here isn't the memory reclamation per se, but
+ * the reference counting race - we need to wait for the
+ * critical section to end before we decrement the value and
+ * possibly destroy the acl objects.
+ */
+ synchronize_rcu();
dns_acl_detach(&localhost);
dns_acl_detach(&localnets);
rcu_read_lock();
- dns_acl_t *localhost = rcu_dereference(source->localhost);
+ /*
+ * We need to acquire the reference inside the critical section.
+ */
+
+ dns_acl_t *localhost = dns_acl_ref(rcu_dereference(source->localhost));
INSIST(DNS_ACL_VALID(localhost));
- dns_acl_t *localnets = rcu_dereference(source->localnets);
+ dns_acl_t *localnets = dns_acl_ref(rcu_dereference(source->localnets));
INSIST(DNS_ACL_VALID(localnets));
- localhost = rcu_xchg_pointer(&target->localhost,
- dns_acl_ref(localhost));
- localnets = rcu_xchg_pointer(&target->localnets,
- dns_acl_ref(localnets));
+ rcu_read_unlock();
+
+ localhost = rcu_xchg_pointer(&target->localhost, localhost);
+ localnets = rcu_xchg_pointer(&target->localnets, localnets);
+
+ /*
+ * This function is called only during (re)configuration, so blocking
+ * a bit is acceptable.
+ *
+ * See the comment above in dns_aclenv_set() for more detail.
+ */
+ synchronize_rcu();
target->match_mapped = source->match_mapped;
#if defined(HAVE_GEOIP2)
target->geoip = source->geoip;
#endif /* if defined(HAVE_GEOIP2) */
- rcu_read_unlock();
-
dns_acl_detach(&localhost);
dns_acl_detach(&localnets);
}
aclenv->magic = 0;
- rcu_read_lock();
- dns_acl_t *localhost = rcu_xchg_pointer(&aclenv->localhost, NULL);
- INSIST(DNS_ACL_VALID(localhost));
-
- dns_acl_t *localnets = rcu_xchg_pointer(&aclenv->localnets, NULL);
- INSIST(DNS_ACL_VALID(localnets));
-
- rcu_read_unlock();
+ /*
+ * The last reference to the aclenv has been detached, so nobody should
+ * be reading from this aclenv. We can destroy the localhost and
+ * localnet directly without swapping the pointers.
+ */
- dns_acl_detach(&localhost);
- dns_acl_detach(&localnets);
+ dns_acl_detach(&aclenv->localhost);
+ dns_acl_detach(&aclenv->localnets);
isc_mem_putanddetach(&aclenv->mctx, aclenv, sizeof(*aclenv));
}