Coverage Report

Created: 2026-07-30 06:31

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