Coverage Report

Created: 2026-04-05 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/bind9/lib/dns/deleg.c
Line
Count
Source
1
/*
2
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3
 *
4
 * SPDX-License-Identifier: MPL-2.0
5
 *
6
 * This Source Code Form is subject to the terms of the Mozilla Public
7
 * License, v. 2.0. If a copy of the MPL was not distributed with this
8
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
9
 *
10
 * See the COPYRIGHT file distributed with this work for additional
11
 * information regarding copyright ownership.
12
 */
13
#include <isc/async.h>
14
#include <isc/magic.h>
15
#include <isc/mem.h>
16
#include <isc/netaddr.h>
17
#include <isc/sieve.h>
18
#include <isc/stdtime.h>
19
#include <isc/urcu.h>
20
#include <isc/uv.h>
21
22
#include <dns/deleg.h>
23
#include <dns/name.h>
24
#include <dns/qp.h>
25
#include <dns/view.h>
26
27
0
#define DELEGDB_NODE_MAGIC   ISC_MAGIC('D', 'e', 'G', 'N')
28
#define VALID_DELEGDB_NODE(node) ISC_MAGIC_VALID(node, DELEGDB_NODE_MAGIC)
29
30
0
#define DELEGDB_MAGIC   ISC_MAGIC('D', 'e', 'G', 'D')
31
#define VALID_DELEGDB(db) ISC_MAGIC_VALID(db, DELEGDB_MAGIC)
32
33
0
#define DELEGDB_MINSIZE (1024 * 1024) /* 1MiB */
34
35
typedef struct delegdb_node delegdb_node_t;
36
37
struct dns_delegdb {
38
  unsigned int magic;
39
40
  /*
41
   * The DB uses its own memory context in order to easily enforce
42
   * overmem policies based on allocations made from this memory context.
43
   */
44
  isc_mem_t *mctx;
45
  isc_refcount_t references;
46
47
  size_t nloops;
48
  ISC_SIEVE(delegdb_node_t) * lru;
49
50
  dns_qpmulti_t *nodes;
51
52
  /*
53
   * Keep track of now many owners are actually using the delegdb. For
54
   * instance:
55
   *
56
   * - During a server reload, the new view will (by default)
57
   *   start owning the existing delegdb from the previous instance of the
58
   *   same view using `dns_delegdb_reuse()`. This will increase `owners`
59
   *   by one.
60
   *
61
   * - Later on, either the old instance of the view (or the new one,
62
   *   in case of reload failure) will call `dns_delegdb_shutdown()` on
63
   *   the delegdb. This will decrement `owners` by one.
64
   *
65
   * If `owners` is bigger than 1 when `dns_delegdb_shutdown()` is called,
66
   * it means the delegdb must not be shutdown because there are other
67
   * owners using it, so `dns_delegdb_shutdown()` bails off in this case.
68
   * (After decrementing `owners`.)
69
   */
70
  isc_refcount_t owners;
71
72
  size_t lowater;
73
  size_t hiwater;
74
};
75
76
static void
77
0
delegdb_destroy(dns_delegdb_t *delegdb) {
78
0
  REQUIRE(VALID_DELEGDB(delegdb));
79
0
  REQUIRE(delegdb->nodes == NULL);
80
81
0
  delegdb->magic = 0;
82
0
  isc_mem_cput(delegdb->mctx, delegdb->lru, delegdb->nloops,
83
0
         sizeof(delegdb->lru[0]));
84
85
0
  isc_mem_putanddetach(&delegdb->mctx, delegdb, sizeof(*delegdb));
86
0
}
87
88
0
ISC_REFCOUNT_IMPL(dns_delegdb, delegdb_destroy);
Unexecuted instantiation: dns_delegdb_ref
Unexecuted instantiation: dns_delegdb_unref
Unexecuted instantiation: dns_delegdb_detach
89
0
90
0
struct delegdb_node {
91
0
  unsigned int magic;
92
0
  dns_delegdb_t *delegdb;
93
0
  isc_refcount_t references;
94
0
95
0
  /* LRU */
96
0
  isc_loop_t *loop;
97
0
  ISC_LINK(delegdb_node_t) link;
98
0
  bool visited;
99
0
100
0
  /*
101
0
   * Used to build a list of nodes to be deleted (when running the
102
0
   * delete tree flow).
103
0
   */
104
0
  ISC_LINK(delegdb_node_t) deadlink;
105
0
106
0
  /*
107
0
   * Immutable node data
108
0
   */
109
0
  size_t size;
110
0
  dns_name_t zonecut;
111
0
  dns_delegset_t *delegset;
112
0
};
113
0
114
0
/*
115
0
 * All node cleanup is done on the node's owning loop so that the node
116
0
 * remains fully valid (name, delegset, SIEVE link) until it is actually
117
0
 * destroyed.  This is important because after a node is removed from the
118
0
 * QP trie, it may still be linked in the owning loop's SIEVE list; if
119
0
 * another thread's eviction could encounter a half-destroyed node, we
120
0
 * would get a use-after-free.  By deferring everything to the owning
121
0
 * loop, the node is intact until the SIEVE unlink happens.
122
0
 */
123
0
static void
124
0
delegdb_node_destroy_async(void *arg) {
125
0
  delegdb_node_t *node = arg;
126
0
  isc_mem_t *mctx = NULL;
127
128
0
  REQUIRE(VALID_DELEGDB_NODE(node));
129
0
  REQUIRE(DNS_DELEGSET_VALID(node->delegset));
130
131
0
  node->magic = 0;
132
133
0
  isc_mem_attach(node->delegdb->mctx, &mctx);
134
135
0
  if (ISC_SIEVE_LINKED(node, link)) {
136
0
    ISC_SIEVE_UNLINK(node->delegdb->lru[isc_tid()], node, link);
137
0
  }
138
139
0
  dns_name_free(&node->zonecut, mctx);
140
0
  dns_delegset_detach(&node->delegset);
141
142
0
  dns_delegdb_detach(&node->delegdb);
143
0
  isc_loop_unref(node->loop);
144
0
  isc_mem_putanddetach(&mctx, node, sizeof(*node));
145
0
}
146
147
static void
148
0
delegdb_node_destroy(delegdb_node_t *node) {
149
0
  REQUIRE(VALID_DELEGDB_NODE(node));
150
151
0
  if (node->loop == isc_loop()) {
152
0
    delegdb_node_destroy_async(node);
153
0
  } else {
154
0
    isc_async_run(node->loop, delegdb_node_destroy_async, node);
155
0
  }
156
0
}
157
158
#ifdef DNS_DELEGDB_NODETRACE
159
#define delegdb_node_ref(ptr) \
160
  delegdb_node__ref(ptr, __func__, __FILE__, __LINE__)
161
#define delegdb_node_unref(ptr) \
162
  delegdb_node__unref(ptr, __func__, __FILE__, __LINE__)
163
ISC_REFCOUNT_STATIC_TRACE_DECL(delegdb_node);
164
ISC_REFCOUNT_STATIC_TRACE_IMPL(delegdb_node, delegdb_node_destroy);
165
#else
166
ISC_REFCOUNT_STATIC_DECL(delegdb_node);
167
0
ISC_REFCOUNT_STATIC_IMPL(delegdb_node, delegdb_node_destroy);
Unexecuted instantiation: deleg.c:delegdb_node_ref
Unexecuted instantiation: deleg.c:delegdb_node_unref
168
0
#endif
169
0
170
0
static void
171
0
dbnode_attach(ISC_ATTR_UNUSED void *uctx, void *pval,
172
0
        ISC_ATTR_UNUSED uint32_t ival) {
173
0
  delegdb_node_t *node = pval;
174
175
0
  REQUIRE(VALID_DELEGDB_NODE(node));
176
0
  delegdb_node_ref(node);
177
0
}
178
179
static void
180
dbnode_detach(ISC_ATTR_UNUSED void *uctx, void *pval,
181
0
        ISC_ATTR_UNUSED uint32_t ival) {
182
0
  delegdb_node_t *node = pval;
183
184
0
  REQUIRE(VALID_DELEGDB_NODE(node));
185
0
  delegdb_node_unref(node);
186
0
}
187
188
static size_t
189
makekey(dns_qpkey_t key, void *uctx ISC_ATTR_UNUSED, void *pval,
190
0
  uint32_t ival ISC_ATTR_UNUSED) {
191
0
  delegdb_node_t *data = pval;
192
0
  return dns_qpkey_fromname(key, &data->zonecut, DNS_DBNAMESPACE_NORMAL);
193
0
}
194
195
static void
196
0
triename(ISC_ATTR_UNUSED void *uctx, char *buf, size_t size) {
197
0
  (void)strncpy(buf, "delegdb", size);
198
0
}
199
200
static dns_qpmethods_t qpmethods = { .attach = dbnode_attach,
201
             .detach = dbnode_detach,
202
             .makekey = makekey,
203
             .triename = triename };
204
205
void
206
0
dns_delegdb_create(dns_delegdb_t **delegdbp) {
207
0
  isc_mem_t *mctx = NULL;
208
0
  dns_delegdb_t *delegdb = NULL;
209
210
0
  REQUIRE(isc_loop_get(isc_tid()) == isc_loop_main());
211
0
  REQUIRE(delegdbp != NULL && *delegdbp == NULL);
212
213
0
  isc_mem_create("dns_delegdb", &mctx);
214
0
  isc_mem_setdestroycheck(mctx, true);
215
216
0
  delegdb = isc_mem_get(mctx, sizeof(*delegdb));
217
0
  *delegdb = (dns_delegdb_t){ .magic = DELEGDB_MAGIC,
218
0
            .mctx = mctx,
219
0
            .references = ISC_REFCOUNT_INITIALIZER(1),
220
0
            .nloops = isc_loopmgr_nloops(),
221
0
            .owners = ISC_REFCOUNT_INITIALIZER(1) };
222
223
0
  dns_qpmulti_create(mctx, &qpmethods, &delegdb->nodes, &delegdb->nodes);
224
225
0
  delegdb->lru = isc_mem_cget(mctx, delegdb->nloops,
226
0
            sizeof(delegdb->lru[0]));
227
0
  for (size_t i = 0; i < delegdb->nloops; i++) {
228
0
    ISC_SIEVE_INIT(delegdb->lru[i]);
229
0
  }
230
231
0
  *delegdbp = delegdb;
232
0
}
233
234
void
235
0
dns_delegdb_reuse(dns_view_t *oldview, dns_view_t *newview) {
236
0
  REQUIRE(isc_loop_get(isc_tid()) == isc_loop_main());
237
0
  REQUIRE(DNS_VIEW_VALID(oldview));
238
0
  REQUIRE(DNS_VIEW_VALID(newview));
239
240
0
  dns_delegdb_attach(oldview->deleg, &newview->deleg);
241
0
  isc_refcount_increment(&oldview->deleg->owners);
242
0
}
243
244
typedef struct nodes_rcu_head {
245
  isc_mem_t *mctx;
246
  dns_qpmulti_t *nodes;
247
  struct rcu_head rcu_head;
248
} nodes_rcu_head_t;
249
250
static void
251
0
deleg_destroy_qpmulti(struct rcu_head *rcu_head) {
252
0
  nodes_rcu_head_t *nrh = caa_container_of(rcu_head, nodes_rcu_head_t,
253
0
             rcu_head);
254
255
0
  dns_qpmulti_destroy(&nrh->nodes);
256
257
0
  isc_mem_putanddetach(&nrh->mctx, nrh, sizeof(*nrh));
258
0
}
259
260
inline static bool
261
0
isactive(delegdb_node_t *node, dns_ttl_t now) {
262
0
  return node->delegset->expires > now;
263
0
}
264
265
static void
266
0
getparentnode(dns_qpchain_t *chain, delegdb_node_t **node, dns_ttl_t now) {
267
0
  size_t len = dns_qpchain_length(chain);
268
269
0
  while (len >= 2) {
270
0
    *node = NULL;
271
0
    dns_qpchain_node(chain, len - 2, (void **)node, NULL);
272
273
0
    if (isactive(*node, now)) {
274
0
      break;
275
0
    }
276
0
    len--;
277
0
  }
278
0
}
279
280
/*
281
 * NOTE: Caller needs to hold a RCU read critical section.
282
 */
283
static isc_result_t
284
dns__deleg_lookup(dns_delegdb_t *delegdb, dns_qpread_t *qpr,
285
      const dns_name_t *name, isc_stdtime_t optnow,
286
      unsigned int options, dns_name_t *zonecut,
287
0
      dns_name_t *deepestzonecut, dns_delegset_t **delegsetp) {
288
0
  isc_result_t result = ISC_R_SUCCESS;
289
0
  delegdb_node_t *node = NULL;
290
0
  isc_stdtime_t now = optnow > 0 ? optnow : isc_stdtime_now();
291
292
0
  dns_qpchain_t chain = {};
293
0
  bool noexact = (options & DNS_DBFIND_NOEXACT) != 0;
294
295
0
  REQUIRE(VALID_DELEGDB(delegdb));
296
0
  REQUIRE(DNS_NAME_VALID(name));
297
0
  REQUIRE(dns_name_hasbuffer(zonecut));
298
0
  REQUIRE(deepestzonecut == NULL || dns_name_hasbuffer(deepestzonecut));
299
300
0
  result = dns_qp_lookup(qpr, name, DNS_DBNAMESPACE_NORMAL, NULL, &chain,
301
0
             (void **)&node, NULL);
302
303
0
  if (result != ISC_R_SUCCESS && result != DNS_R_PARTIALMATCH) {
304
0
    return ISC_R_NOTFOUND;
305
0
  }
306
0
  INSIST(VALID_DELEGDB_NODE(node));
307
308
0
  if (deepestzonecut != NULL) {
309
0
    dns_name_copy(&node->zonecut, deepestzonecut);
310
0
  }
311
312
0
  if (result == ISC_R_SUCCESS && (noexact || !isactive(node, now))) {
313
0
    getparentnode(&chain, &node, now);
314
0
  } else if (result == DNS_R_PARTIALMATCH && !isactive(node, now)) {
315
0
    getparentnode(&chain, &node, now);
316
0
  }
317
318
0
  result = isactive(node, now) ? ISC_R_SUCCESS : ISC_R_NOTFOUND;
319
0
  if (result == ISC_R_SUCCESS) {
320
0
    dns_name_copy(&node->zonecut, zonecut);
321
0
    INSIST(node->delegset);
322
0
    dns_delegset_attach(node->delegset, delegsetp);
323
0
    ISC_SIEVE_MARK(node, visited);
324
0
  } else {
325
    /*
326
     * FIXME: if we lookup something that has expired, we need
327
     * either the "deadnodes" (see qpcache) mechanism here - or call
328
     * something like isc_async_run(delete_me, node).
329
     */
330
0
  }
331
332
0
  return result;
333
0
}
334
335
isc_result_t
336
dns_delegdb_lookup(dns_delegdb_t *delegdb, const dns_name_t *name,
337
       isc_stdtime_t now, unsigned int options, dns_name_t *zonecut,
338
0
       dns_name_t *deepestzonecut, dns_delegset_t **delegsetp) {
339
0
  isc_result_t result = ISC_R_SHUTTINGDOWN;
340
0
  dns_qpmulti_t *nodes = NULL;
341
0
  dns_qpread_t qpr = {};
342
343
0
  rcu_read_lock();
344
0
  nodes = rcu_dereference(delegdb->nodes);
345
0
  if (nodes != NULL) {
346
0
    dns_qpmulti_query(nodes, &qpr);
347
348
0
    result = dns__deleg_lookup(delegdb, &qpr, name, now, options,
349
0
             zonecut, deepestzonecut, delegsetp);
350
0
    dns_qpread_destroy(nodes, &qpr);
351
0
  }
352
0
  rcu_read_unlock();
353
354
0
  return result;
355
0
}
356
357
void
358
0
dns_delegset_allocset(dns_delegdb_t *delegdb, dns_delegset_t **delegsetp) {
359
0
  REQUIRE(VALID_DELEGDB(delegdb));
360
0
  REQUIRE(delegsetp != NULL && *delegsetp == NULL);
361
362
0
  dns_delegset_t *delegset = isc_mem_get(delegdb->mctx,
363
0
                 sizeof(*delegset));
364
0
  *delegset = (dns_delegset_t){
365
0
    .magic = DNS_DELEGSET_MAGIC,
366
0
    .references = ISC_REFCOUNT_INITIALIZER(1),
367
0
    .delegs = ISC_LIST_INITIALIZER,
368
0
  };
369
0
  isc_mem_attach(delegdb->mctx, &delegset->mctx);
370
371
0
  *delegsetp = delegset;
372
0
}
373
374
void
375
dns_delegset_allocdeleg(dns_delegset_t *delegset, dns_deleg_type_t type,
376
0
      dns_deleg_t **delegp) {
377
0
  dns_deleg_t *deleg = NULL;
378
379
0
  REQUIRE(DNS_DELEGSET_VALID(delegset));
380
0
  REQUIRE(delegp != NULL && *delegp == NULL);
381
0
  REQUIRE(type != DNS_DELEGTYPE_UNDEFINED);
382
383
0
  deleg = isc_mem_get(delegset->mctx, sizeof(*deleg));
384
0
  *deleg = (dns_deleg_t){ .addresses = ISC_LIST_INITIALIZER,
385
0
        .names = ISC_LIST_INITIALIZER,
386
0
        .type = type,
387
0
        .link = ISC_LINK_INITIALIZER };
388
389
0
  ISC_LIST_APPEND(delegset->delegs, deleg, link);
390
0
  *delegp = deleg;
391
0
}
392
393
void
394
dns_delegset_addaddr(dns_delegset_t *delegset, dns_deleg_t *deleg,
395
0
         const isc_netaddr_t *addr) {
396
0
  isc_netaddrlink_t *addrlink = NULL;
397
398
0
  REQUIRE(DNS_DELEGSET_VALID(delegset));
399
0
  REQUIRE(deleg != NULL);
400
0
  REQUIRE(addr != NULL);
401
0
  REQUIRE(deleg->type == DNS_DELEGTYPE_DELEG_ADDRESSES ||
402
0
    deleg->type == DNS_DELEGTYPE_NS_GLUES);
403
404
0
  addrlink = isc_mem_get(delegset->mctx, sizeof(*addrlink));
405
0
  *addrlink = (isc_netaddrlink_t){ .addr = *addr,
406
0
           .link = ISC_LINK_INITIALIZER };
407
408
0
  ISC_LIST_APPEND(deleg->addresses, addrlink, link);
409
0
}
410
411
static void
412
addname(dns_delegset_t *delegset, dns_namelist_t *list,
413
0
  const dns_name_t *name) {
414
0
  dns_name_t *clone = NULL;
415
416
0
  REQUIRE(DNS_DELEGSET_VALID(delegset));
417
0
  REQUIRE(DNS_NAME_VALID(name));
418
419
0
  clone = isc_mem_get(delegset->mctx, sizeof(*clone));
420
0
  dns_name_init(clone);
421
0
  dns_name_dup(name, delegset->mctx, clone);
422
0
  ISC_LIST_APPEND(*list, clone, link);
423
0
}
424
425
void
426
dns_delegset_adddelegparam(dns_delegset_t *delegset, dns_deleg_t *deleg,
427
0
         const dns_name_t *name) {
428
0
  REQUIRE(deleg != NULL);
429
0
  REQUIRE(deleg->type == DNS_DELEGTYPE_DELEG_PARAMS);
430
0
  addname(delegset, &deleg->names, name);
431
0
}
432
433
void
434
dns_delegset_addns(dns_delegset_t *delegset, dns_deleg_t *deleg,
435
0
       const dns_name_t *name) {
436
0
  REQUIRE(deleg != NULL);
437
438
0
  REQUIRE(deleg->type == DNS_DELEGTYPE_DELEG_NAMES ||
439
0
    deleg->type == DNS_DELEGTYPE_NS_NAMES);
440
0
  addname(delegset, &deleg->names, name);
441
0
}
442
443
static void
444
0
delegdb_cleanup(dns_delegdb_t *delegdb, dns_qpmulti_t *nodes) {
445
0
  dns_qp_t *qp = NULL;
446
0
  delegdb_node_t *node = NULL;
447
0
  size_t reclaimed = 0;
448
0
  size_t requested = 0;
449
450
0
  if (!isc_mem_isovermem(delegdb->mctx)) {
451
0
    return;
452
0
  }
453
0
  requested = delegdb->hiwater - delegdb->lowater;
454
455
0
  dns_qpmulti_write(nodes, &qp);
456
457
0
  while (reclaimed < requested) {
458
0
    node = ISC_SIEVE_NEXT(delegdb->lru[isc_tid()], visited, link);
459
460
0
    if (node == NULL) {
461
0
      break;
462
0
    }
463
0
    reclaimed += node->size;
464
465
0
    ISC_SIEVE_UNLINK(delegdb->lru[isc_tid()], node, link);
466
0
    (void)dns_qp_deletename(qp, &node->zonecut,
467
0
          DNS_DBNAMESPACE_NORMAL, NULL, NULL);
468
0
  }
469
470
0
  dns_qp_compact(qp, DNS_QPGC_ALL);
471
0
  dns_qpmulti_commit(nodes, &qp);
472
0
}
473
474
static size_t
475
0
delegset_size(dns_delegset_t *delegset) {
476
0
  size_t sz = 0;
477
478
0
  sz += sizeof(*delegset);
479
0
  ISC_LIST_FOREACH(delegset->delegs, deleg, link) {
480
0
    sz += sizeof(*deleg);
481
0
    ISC_LIST_FOREACH(deleg->addresses, address, link) {
482
0
      sz += sizeof(*address);
483
0
    }
484
0
    ISC_LIST_FOREACH(deleg->names, name, link) {
485
0
      sz += sizeof(*name) + dns_name_size(name);
486
0
    }
487
0
  }
488
489
0
  return sz;
490
0
}
491
492
static size_t
493
0
delegdb_node_size(const dns_name_t *zonecut, dns_delegset_t *delegset) {
494
0
  size_t sz = 0;
495
496
0
  sz += sizeof(delegdb_node_t);
497
0
  sz += dns_name_size(zonecut);
498
0
  sz += delegset_size(delegset);
499
500
0
  return sz;
501
0
}
502
503
static void
504
delegdb_node_prepare(dns_delegdb_t *delegdb, dns_qpmulti_t *nodes,
505
         isc_stdtime_t now, dns_ttl_t ttl,
506
         const dns_name_t *zonecut, dns_delegset_t *delegset,
507
0
         delegdb_node_t **nodep) {
508
0
  delegdb_cleanup(delegdb, nodes);
509
510
0
  if (ttl == 0) {
511
0
    ttl = 1;
512
0
  }
513
0
  delegset->expires = ttl + now;
514
515
0
  *nodep = isc_mem_get(delegdb->mctx, sizeof(**nodep));
516
0
  **nodep =
517
0
    (delegdb_node_t){ .magic = DELEGDB_NODE_MAGIC,
518
0
          .references = ISC_REFCOUNT_INITIALIZER(1),
519
0
          .zonecut = DNS_NAME_INITEMPTY,
520
0
          .link = ISC_LINK_INITIALIZER,
521
0
          .deadlink = ISC_LINK_INITIALIZER,
522
0
          .size = delegdb_node_size(zonecut, delegset),
523
0
          .loop = isc_loop_ref(isc_loop()) };
524
525
0
  dns_delegdb_attach(delegdb, &(*nodep)->delegdb);
526
0
  dns_delegset_attach(delegset, &(*nodep)->delegset);
527
0
  dns_name_dup(zonecut, delegdb->mctx, &(*nodep)->zonecut);
528
0
}
529
530
isc_result_t
531
dns_delegset_insert(dns_delegdb_t *delegdb, const dns_name_t *zonecut,
532
0
        dns_ttl_t ttl, dns_delegset_t *delegset) {
533
0
  isc_result_t result;
534
0
  delegdb_node_t *node = NULL;
535
0
  dns_qp_t *qp = NULL;
536
0
  dns_qpread_t qpr = {};
537
0
  isc_stdtime_t now = isc_stdtime_now();
538
0
  dns_qpmulti_t *nodes = NULL;
539
540
0
  REQUIRE(VALID_DELEGDB(delegdb));
541
0
  REQUIRE(DNS_NAME_VALID(zonecut));
542
0
  REQUIRE(DNS_DELEGSET_VALID(delegset));
543
544
  /*
545
   * Only delegset allocated by the delegdb memory context can be added in
546
   * the delegdb. This exclude transient delegset built from rdataset (see
547
   * dns_delegset_fromrdataset()).
548
   */
549
0
  REQUIRE(delegset->mctx == delegdb->mctx);
550
551
0
  rcu_read_lock();
552
0
  nodes = rcu_dereference(delegdb->nodes);
553
0
  if (nodes == NULL) {
554
0
    rcu_read_unlock();
555
0
    return ISC_R_SHUTTINGDOWN;
556
0
  }
557
558
  /*
559
   * First, check (without write txn) if the node already exists and is
560
   * still valid.
561
   */
562
0
  dns_qpmulti_query(nodes, &qpr);
563
0
  result = dns_qp_lookup(&qpr, zonecut, DNS_DBNAMESPACE_NORMAL, NULL,
564
0
             NULL, (void **)&node, NULL);
565
0
  if (result == ISC_R_SUCCESS) {
566
0
    INSIST(VALID_DELEGDB_NODE(node));
567
0
    if (node->delegset->expires > now) {
568
0
      dns_qpread_destroy(nodes, &qpr);
569
0
      CLEANUP(ISC_R_EXISTS);
570
0
    }
571
0
  }
572
0
  dns_qpread_destroy(nodes, &qpr);
573
574
  /*
575
   * We're about to add a new delegation, check for state of overmem, and
576
   * clean up expired/least recently used delegation, then allocate and
577
   * initialize a new node.
578
   */
579
0
  delegdb_node_prepare(delegdb, nodes, now, ttl, zonecut, delegset,
580
0
           &node);
581
582
  /*
583
   * Add the node in the DB
584
   */
585
0
  dns_qpmulti_write(nodes, &qp);
586
0
  if (result == ISC_R_SUCCESS) {
587
    /*
588
     * A node at the same zonecut exists, and it is expired. Ignore
589
     * the return value, in case the overriden node would be removed
590
     * in meantime by someone else.
591
     */
592
0
    (void)dns_qp_deletename(qp, zonecut, DNS_DBNAMESPACE_NORMAL,
593
0
          NULL, NULL);
594
0
  }
595
596
0
  result = dns_qp_insert(qp, node, 0);
597
0
  if (result != ISC_R_SUCCESS) {
598
    /*
599
     * Someone else added the node before (and there was no node to
600
     * delete).
601
     */
602
603
0
    delegdb_node_unref(node);
604
605
    /*
606
     * Since not using an update (but write) transaction,
607
     * _rollback() won't work here.
608
     */
609
0
    dns_qpmulti_commit(nodes, &qp);
610
0
    CLEANUP(ISC_R_EXISTS);
611
0
  }
612
613
  /*
614
   * The new delegation is added, and can be referenced by SIEVE
615
   */
616
0
  ISC_SIEVE_INSERT(delegdb->lru[isc_tid()], node, link);
617
618
0
  delegdb_node_unref(node);
619
0
  dns_qp_compact(qp, DNS_QPGC_MAYBE);
620
0
  dns_qpmulti_commit(nodes, &qp);
621
622
0
cleanup:
623
0
  rcu_read_unlock();
624
625
0
  return result;
626
0
}
627
628
static void
629
0
delegset_destroy(dns_delegset_t *delegset) {
630
0
  REQUIRE(DNS_DELEGSET_VALID(delegset));
631
632
0
  delegset->magic = 0;
633
0
  ISC_LIST_FOREACH(delegset->delegs, deleg, link) {
634
0
    deleg->type = DNS_DELEGTYPE_UNDEFINED;
635
636
0
    ISC_LIST_UNLINK(delegset->delegs, deleg, link);
637
638
0
    ISC_LIST_FOREACH(deleg->addresses, address, link) {
639
0
      ISC_LIST_UNLINK(deleg->addresses, address, link);
640
0
      isc_mem_put(delegset->mctx, address, sizeof(*address));
641
0
    }
642
643
0
    ISC_LIST_FOREACH(deleg->names, nameserver, link) {
644
0
      ISC_LIST_UNLINK(deleg->names, nameserver, link);
645
0
      dns_name_free(nameserver, delegset->mctx);
646
0
      isc_mem_put(delegset->mctx, nameserver,
647
0
            sizeof(*nameserver));
648
0
    }
649
650
0
    isc_mem_put(delegset->mctx, deleg, sizeof(*deleg));
651
0
  }
652
653
0
  isc_mem_putanddetach(&delegset->mctx, delegset, sizeof(*delegset));
654
0
}
655
0
ISC_REFCOUNT_IMPL(dns_delegset, delegset_destroy);
Unexecuted instantiation: dns_delegset_ref
Unexecuted instantiation: dns_delegset_unref
Unexecuted instantiation: dns_delegset_detach
656
0
657
0
static void
658
0
tostring_namelist(dns_namelist_t *namelist, const char *id, FILE *fp) {
659
0
  if (!ISC_LIST_EMPTY(*namelist)) {
660
0
    fprintf(fp, " %s=", id);
661
0
    ISC_LIST_FOREACH(*namelist, name, link) {
662
0
      isc_buffer_t nameb;
663
0
      char bdata[DNS_NAME_MAXWIRE] = { 0 };
664
665
0
      isc_buffer_init(&nameb, bdata, sizeof(bdata));
666
0
      dns_name_totext(name, 0, &nameb);
667
0
      fprintf(fp, "%s", bdata);
668
669
0
      if (name != ISC_LIST_TAIL(*namelist)) {
670
0
        fprintf(fp, ",");
671
0
      }
672
0
    }
673
0
  }
674
0
}
675
676
static void
677
0
deleg_tostring_addresses(dns_deleg_t *deleg, FILE *fp) {
678
0
  bool hasv4 = false;
679
0
  bool hasv6 = false;
680
681
0
  ISC_LIST_FOREACH(deleg->addresses, address, link) {
682
0
    if (address->addr.family == AF_INET) {
683
0
      hasv4 = true;
684
0
    } else {
685
0
      hasv6 = true;
686
0
    }
687
0
  }
688
689
0
  if (hasv4) {
690
0
    bool first = true;
691
692
0
    fprintf(fp, " server-ipv4=");
693
0
    ISC_LIST_FOREACH(deleg->addresses, address, link) {
694
0
      char addrstr[] = "255.255.255.255";
695
696
0
      if (address->addr.family == AF_INET6) {
697
0
        continue;
698
0
      }
699
700
0
      if (!first) {
701
0
        fprintf(fp, ",");
702
0
      }
703
0
      first = false;
704
705
0
      inet_ntop(AF_INET, &address->addr.type, addrstr,
706
0
          sizeof(addrstr));
707
0
      fprintf(fp, "%s", addrstr);
708
0
    }
709
0
  }
710
711
0
  if (hasv6) {
712
0
    bool first = true;
713
714
0
    fprintf(fp, " server-ipv6=");
715
0
    ISC_LIST_FOREACH(deleg->addresses, address, link) {
716
0
      char addrstr[INET6_ADDRSTRLEN];
717
718
0
      if (address->addr.family == AF_INET) {
719
0
        continue;
720
0
      }
721
722
0
      if (!first) {
723
0
        fprintf(fp, ",");
724
0
      }
725
0
      first = false;
726
727
0
      inet_ntop(AF_INET6, &address->addr.type, addrstr,
728
0
          sizeof(addrstr));
729
0
      fprintf(fp, "%s", addrstr);
730
0
    }
731
0
  }
732
0
}
733
734
static void
735
delegset_tostring(const dns_name_t *zonecut, dns_delegset_t *delegset,
736
0
      isc_stdtime_t now, bool expired, FILE *fp) {
737
0
  ISC_LIST_FOREACH(delegset->delegs, deleg, link) {
738
0
    isc_buffer_t zonecutb;
739
0
    char bdata[DNS_NAME_MAXWIRE];
740
0
    dns_ttl_t ttl = 0;
741
742
0
    if (delegset->expires > now) {
743
0
      ttl = delegset->expires - now;
744
0
    } else {
745
0
      INSIST(expired);
746
0
    }
747
748
0
    isc_buffer_init(&zonecutb, bdata, sizeof(bdata));
749
0
    dns_name_totext(zonecut, 0, &zonecutb);
750
0
    fprintf(fp, "%s %u DELEG", bdata, ttl);
751
752
0
    if (deleg->type == DNS_DELEGTYPE_DELEG_ADDRESSES ||
753
0
        deleg->type == DNS_DELEGTYPE_NS_GLUES)
754
0
    {
755
0
      deleg_tostring_addresses(deleg, fp);
756
0
    } else if (deleg->type == DNS_DELEGTYPE_DELEG_NAMES ||
757
0
         deleg->type == DNS_DELEGTYPE_NS_NAMES)
758
0
    {
759
0
      tostring_namelist(&deleg->names, "server-name", fp);
760
0
    } else if (deleg->type == DNS_DELEGTYPE_DELEG_PARAMS) {
761
0
      tostring_namelist(&deleg->names, "include-delegparam",
762
0
            fp);
763
0
    } else {
764
0
      UNREACHABLE();
765
0
    }
766
767
0
    fprintf(fp, "\n");
768
0
  }
769
0
}
770
771
void
772
0
dns_delegdb_dump(dns_delegdb_t *delegdb, bool expired, FILE *fp) {
773
0
  REQUIRE(VALID_DELEGDB(delegdb));
774
0
  REQUIRE(fp != NULL);
775
776
0
  dns_qpiter_t it;
777
0
  dns_qpread_t qpr = {};
778
0
  delegdb_node_t *node = NULL;
779
0
  isc_stdtime_t now = isc_stdtime_now();
780
0
  dns_qpmulti_t *nodes = NULL;
781
782
0
  rcu_read_lock();
783
0
  nodes = rcu_dereference(delegdb->nodes);
784
0
  if (nodes == NULL) {
785
0
    rcu_read_unlock();
786
0
    return;
787
0
  }
788
789
0
  dns_qpmulti_query(nodes, &qpr);
790
791
0
  dns_qpiter_init(&qpr, &it);
792
0
  while (dns_qpiter_next(&it, (void **)&node, NULL) == ISC_R_SUCCESS) {
793
0
    if (!expired && !isactive(node, now)) {
794
0
      continue;
795
0
    }
796
797
0
    delegset_tostring(&node->zonecut, node->delegset, now, expired,
798
0
          fp);
799
0
  }
800
801
0
  dns_qpread_destroy(nodes, &qpr);
802
803
0
  rcu_read_unlock();
804
0
}
805
806
void
807
dns_delegset_fromnsrdataset(dns_rdataset_t *rdataset,
808
0
          dns_delegset_t **delegsetp) {
809
0
  dns_delegset_t *delegset = NULL;
810
0
  dns_deleg_t *deleg = NULL;
811
812
0
  if (rdataset == NULL || !dns_rdataset_isassociated(rdataset) ||
813
0
      delegsetp == NULL || *delegsetp != NULL)
814
0
  {
815
0
    return;
816
0
  }
817
818
0
  REQUIRE(rdataset->type == dns_rdatatype_ns);
819
820
0
  delegset = isc_mem_get(isc_g_mctx, sizeof(*delegset));
821
0
  *delegset = (dns_delegset_t){
822
0
    .magic = DNS_DELEGSET_MAGIC,
823
0
    .references = ISC_REFCOUNT_INITIALIZER(1),
824
0
    .delegs = ISC_LIST_INITIALIZER,
825
0
    .expires = rdataset->ttl + isc_stdtime_now(),
826
0
    .staticstub = rdataset->attributes.staticstub
827
0
  };
828
0
  isc_mem_attach(isc_g_mctx, &delegset->mctx);
829
830
0
  deleg = isc_mem_get(isc_g_mctx, sizeof(*deleg));
831
0
  *deleg = (dns_deleg_t){ .addresses = ISC_LIST_INITIALIZER,
832
0
        .names = ISC_LIST_INITIALIZER,
833
0
        .type = DNS_DELEGTYPE_NS_NAMES,
834
0
        .link = ISC_LINK_INITIALIZER };
835
0
  ISC_LIST_APPEND(delegset->delegs, deleg, link);
836
837
0
  DNS_RDATASET_FOREACH(rdataset) {
838
0
    dns_rdata_t rdata = DNS_RDATA_INIT;
839
0
    dns_rdata_ns_t ns;
840
841
0
    dns_rdataset_current(rdataset, &rdata);
842
0
    dns_rdata_tostruct(&rdata, &ns, NULL);
843
0
    dns_delegset_addns(delegset, deleg, &ns.name);
844
0
  }
845
846
0
  *delegsetp = delegset;
847
0
}
848
849
static isc_result_t
850
0
deleg_deletetree(dns_qp_t *qp, const dns_name_t *name) {
851
0
  isc_result_t result;
852
0
  delegdb_node_t *node = NULL;
853
0
  dns_qpiter_t it;
854
0
  ISC_LIST(delegdb_node_t) deadnodes = ISC_LIST_INITIALIZER;
855
856
0
  result = dns_qp_lookup(qp, name, DNS_DBNAMESPACE_NORMAL, &it, NULL,
857
0
             (void **)&node, NULL);
858
0
  if (result != ISC_R_SUCCESS) {
859
0
    goto out;
860
0
  }
861
862
0
  INSIST(VALID_DELEGDB_NODE(node));
863
0
  do {
864
    /*
865
     * Because QP doesn't allow deleting a node while using the
866
     * iterator, the approach is different than `deleg_deletenode()`
867
     * here. Instead of removing the node immediately, we add it
868
     * into a list that we'll go through after, then delete each
869
     * node.
870
     */
871
0
    ISC_LIST_APPEND(deadnodes, node, deadlink);
872
873
0
    result = dns_qpiter_next(&it, (void **)&node, NULL);
874
0
    if (result == ISC_R_NOMORE) {
875
0
      result = ISC_R_SUCCESS;
876
0
      break;
877
0
    }
878
879
0
    INSIST(VALID_DELEGDB_NODE(node));
880
0
    if (!dns_name_issubdomain(&node->zonecut, name)) {
881
0
      break;
882
0
    }
883
0
  } while (result == ISC_R_SUCCESS);
884
885
0
out:
886
0
  if (ISC_LIST_EMPTY(deadnodes)) {
887
0
    result = ISC_R_NOTFOUND;
888
0
  } else {
889
    /*
890
     * Let's actually delete the deadnodes!
891
     */
892
0
    ISC_LIST_FOREACH(deadnodes, deadnode, deadlink) {
893
0
      result = dns_qp_deletename(qp, &deadnode->zonecut,
894
0
               DNS_DBNAMESPACE_NORMAL, NULL,
895
0
               NULL);
896
0
      INSIST(result == ISC_R_SUCCESS);
897
0
    }
898
0
  }
899
900
0
  return result;
901
0
}
902
903
static isc_result_t
904
0
deleg_deletenode(dns_qp_t *qp, const dns_name_t *name) {
905
0
  return dns_qp_deletename(qp, name, DNS_DBNAMESPACE_NORMAL, NULL, NULL);
906
0
}
907
908
isc_result_t
909
0
dns_delegdb_delete(dns_delegdb_t *delegdb, const dns_name_t *name, bool tree) {
910
0
  REQUIRE(VALID_DELEGDB(delegdb));
911
0
  REQUIRE(DNS_NAME_VALID(name));
912
913
0
  dns_qpmulti_t *nodes = NULL;
914
0
  dns_qp_t *qp = NULL;
915
0
  isc_result_t result = ISC_R_SHUTTINGDOWN;
916
917
0
  rcu_read_lock();
918
0
  nodes = rcu_dereference(delegdb->nodes);
919
0
  if (nodes != NULL) {
920
0
    dns_qpmulti_write(nodes, &qp);
921
0
    if (tree) {
922
0
      result = deleg_deletetree(qp, name);
923
0
    } else {
924
0
      result = deleg_deletenode(qp, name);
925
0
    }
926
0
    if (result == ISC_R_SUCCESS) {
927
0
      dns_qp_compact(qp, DNS_QPGC_MAYBE);
928
0
    }
929
0
    dns_qpmulti_commit(nodes, &qp);
930
0
  }
931
0
  rcu_read_unlock();
932
933
0
  return result;
934
0
}
935
936
static void
937
0
delegdb_shutdown_async(void *arg) {
938
0
  dns_delegdb_t *delegdb = arg;
939
940
0
  REQUIRE(isc_loop_get(isc_tid()) == isc_loop_main());
941
0
  REQUIRE(delegdb != NULL && VALID_DELEGDB(delegdb));
942
0
  if (isc_refcount_decrement(&delegdb->owners) == 1) {
943
0
    dns_qpmulti_t *nodes = rcu_xchg_pointer(&delegdb->nodes, NULL);
944
945
0
    if (nodes != NULL) {
946
0
      nodes_rcu_head_t *nrh = isc_mem_get(delegdb->mctx,
947
0
                  sizeof(*nrh));
948
0
      *nrh = (nodes_rcu_head_t){
949
0
        .mctx = isc_mem_ref(delegdb->mctx),
950
0
        .nodes = nodes,
951
0
      };
952
0
      call_rcu(&nrh->rcu_head, deleg_destroy_qpmulti);
953
0
    }
954
0
  }
955
0
}
956
957
void
958
0
dns_delegdb_shutdown(dns_delegdb_t *delegdb) {
959
0
  if (isc_loop_get(isc_tid()) == isc_loop_main()) {
960
0
    delegdb_shutdown_async(delegdb);
961
0
  } else {
962
0
    isc_async_run(isc_loop_main(), delegdb_shutdown_async, delegdb);
963
0
  }
964
0
}
965
966
void
967
0
dns_delegdb_setsize(dns_delegdb_t *delegdb, size_t size) {
968
0
  REQUIRE(VALID_DELEGDB(delegdb));
969
970
0
  if (size != 0 && size < DELEGDB_MINSIZE) {
971
0
    size = DELEGDB_MINSIZE;
972
0
  }
973
974
0
  delegdb->hiwater = size - (size >> 3); /* Approximately 7/8ths. */
975
0
  delegdb->lowater = size - (size >> 2); /* Approximately 3/4ths. */
976
977
0
  if (size == 0 || delegdb->hiwater == 0 || delegdb->lowater == 0) {
978
0
    isc_mem_clearwater(delegdb->mctx);
979
980
    /*
981
     * TODO: Is it worth a warning if size > 0? Sounds like
982
     * implicit overmem bypass, so the user should be warned...
983
     */
984
0
  } else {
985
0
    isc_mem_setwater(delegdb->mctx, delegdb->hiwater,
986
0
         delegdb->lowater);
987
0
  }
988
0
}