Coverage Report

Created: 2025-11-24 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/bind9/lib/dns/rdataset.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
14
/*! \file */
15
16
#include <inttypes.h>
17
#include <stdbool.h>
18
#include <stdlib.h>
19
20
#include <isc/buffer.h>
21
#include <isc/mem.h>
22
#include <isc/random.h>
23
#include <isc/result.h>
24
#include <isc/serial.h>
25
#include <isc/util.h>
26
27
#include <dns/compress.h>
28
#include <dns/fixedname.h>
29
#include <dns/name.h>
30
#include <dns/ncache.h>
31
#include <dns/rdata.h>
32
#include <dns/rdataset.h>
33
#include <dns/types.h>
34
35
#define MAX_SHUFFLE 100
36
thread_local dns_rdata_t dns__rdataset_rdatas[MAX_SHUFFLE];
37
38
static const char *trustnames[] = {
39
  "none",     "pending-additional",
40
  "pending-answer", "additional",
41
  "glue",     "answer",
42
  "authauthority",  "authanswer",
43
  "secure",   "local" /* aka ultimate */
44
};
45
46
const char *
47
0
dns_trust_totext(dns_trust_t trust) {
48
0
  if (trust >= sizeof(trustnames) / sizeof(*trustnames)) {
49
0
    return "bad";
50
0
  }
51
0
  return trustnames[trust];
52
0
}
53
54
void
55
0
dns_rdataset_init(dns_rdataset_t *rdataset) {
56
  /*
57
   * Make 'rdataset' a valid, disassociated rdataset.
58
   */
59
60
0
  REQUIRE(rdataset != NULL);
61
62
0
  *rdataset = (dns_rdataset_t){
63
0
    .magic = DNS_RDATASET_MAGIC,
64
0
    .link = ISC_LINK_INITIALIZER,
65
0
  };
66
0
}
67
68
void
69
0
dns_rdataset_invalidate(dns_rdataset_t *rdataset) {
70
  /*
71
   * Invalidate 'rdataset'.
72
   */
73
74
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
75
0
  REQUIRE(rdataset->methods == NULL);
76
77
0
  *rdataset = (dns_rdataset_t){
78
0
    .magic = 0,
79
0
    .link = ISC_LINK_INITIALIZER,
80
0
  };
81
0
}
82
83
void
84
0
dns__rdataset_disassociate(dns_rdataset_t *rdataset DNS__DB_FLARG) {
85
  /*
86
   * Disassociate 'rdataset' from its rdata, allowing it to be reused.
87
   */
88
89
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
90
0
  REQUIRE(rdataset->methods != NULL);
91
92
0
  if (rdataset->methods->disassociate != NULL) {
93
0
    (rdataset->methods->disassociate)(rdataset DNS__DB_FLARG_PASS);
94
0
  }
95
0
  *rdataset = (dns_rdataset_t){
96
0
    .magic = DNS_RDATASET_MAGIC,
97
0
    .link = ISC_LINK_INITIALIZER,
98
0
  };
99
0
}
100
101
bool
102
0
dns_rdataset_isassociated(dns_rdataset_t *rdataset) {
103
  /*
104
   * Is 'rdataset' associated?
105
   */
106
107
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
108
109
0
  if (rdataset->methods != NULL) {
110
0
    return true;
111
0
  }
112
113
0
  return false;
114
0
}
115
116
static isc_result_t
117
0
question_cursor(dns_rdataset_t *rdataset ISC_ATTR_UNUSED) {
118
0
  return ISC_R_NOMORE;
119
0
}
120
121
static void
122
0
question_clone(dns_rdataset_t *source, dns_rdataset_t *target DNS__DB_FLARG) {
123
0
  *target = *source;
124
0
}
125
126
static dns_rdatasetmethods_t question_methods = {
127
  .first = question_cursor,
128
  .next = question_cursor,
129
  .clone = question_clone,
130
};
131
132
void
133
dns_rdataset_makequestion(dns_rdataset_t *rdataset, dns_rdataclass_t rdclass,
134
0
        dns_rdatatype_t type) {
135
  /*
136
   * Make 'rdataset' a valid, associated, question rdataset, with a
137
   * question class of 'rdclass' and type 'type'.
138
   */
139
140
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
141
0
  REQUIRE(rdataset->methods == NULL);
142
143
0
  rdataset->methods = &question_methods;
144
0
  rdataset->rdclass = rdclass;
145
0
  rdataset->type = type;
146
0
  rdataset->attributes.question = true;
147
0
}
148
149
unsigned int
150
0
dns_rdataset_count(dns_rdataset_t *rdataset) {
151
  /*
152
   * Return the number of records in 'rdataset'.
153
   */
154
155
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
156
0
  REQUIRE(rdataset->methods != NULL);
157
0
  REQUIRE(rdataset->methods->count != NULL);
158
159
0
  return (rdataset->methods->count)(rdataset);
160
0
}
161
162
void
163
dns__rdataset_clone(dns_rdataset_t *source,
164
0
        dns_rdataset_t *target DNS__DB_FLARG) {
165
  /*
166
   * Make 'target' refer to the same rdataset as 'source'.
167
   */
168
169
0
  REQUIRE(DNS_RDATASET_VALID(source));
170
0
  REQUIRE(source->methods != NULL);
171
0
  REQUIRE(DNS_RDATASET_VALID(target));
172
0
  REQUIRE(target->methods == NULL);
173
174
0
  (source->methods->clone)(source, target DNS__DB_FLARG_PASS);
175
0
}
176
177
isc_result_t
178
0
dns_rdataset_first(dns_rdataset_t *rdataset) {
179
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
180
0
  REQUIRE(rdataset->methods != NULL);
181
0
  REQUIRE(rdataset->methods->first != NULL);
182
183
0
  isc_result_t result = rdataset->methods->first(rdataset);
184
0
  ENSURE(result == ISC_R_SUCCESS || result == ISC_R_NOMORE);
185
0
  return result;
186
0
}
187
188
isc_result_t
189
0
dns_rdataset_next(dns_rdataset_t *rdataset) {
190
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
191
0
  REQUIRE(rdataset->methods != NULL);
192
0
  REQUIRE(rdataset->methods->next != NULL);
193
194
0
  isc_result_t result = rdataset->methods->next(rdataset);
195
0
  ENSURE(result == ISC_R_SUCCESS || result == ISC_R_NOMORE);
196
0
  return result;
197
0
}
198
199
void
200
0
dns_rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
201
  /*
202
   * Make 'rdata' refer to the current rdata.
203
   */
204
205
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
206
0
  REQUIRE(rdataset->methods != NULL);
207
0
  REQUIRE(rdataset->methods->current != NULL);
208
209
0
  (rdataset->methods->current)(rdataset, rdata);
210
0
}
211
212
0
#define WANT_CYCLIC(r) (((r)->attributes.order == dns_order_cyclic))
213
214
static isc_result_t
215
towire_addtypeclass(dns_rdataset_t *rdataset, const dns_name_t *name,
216
        dns_compress_t *cctx, isc_buffer_t *target,
217
0
        isc_buffer_t *rrbuffer, size_t extralen) {
218
0
  isc_region_t r;
219
0
  isc_result_t result;
220
0
  size_t headlen;
221
222
0
  *rrbuffer = *target;
223
0
  dns_compress_setpermitted(cctx, true);
224
0
  result = dns_name_towire(name, cctx, target);
225
0
  if (result != ISC_R_SUCCESS) {
226
0
    return result;
227
0
  }
228
0
  headlen = sizeof(dns_rdataclass_t) + sizeof(dns_rdatatype_t) + extralen;
229
0
  isc_buffer_availableregion(target, &r);
230
0
  if (r.length < headlen) {
231
0
    return ISC_R_NOSPACE;
232
0
  }
233
0
  isc_buffer_putuint16(target, rdataset->type);
234
0
  isc_buffer_putuint16(target, rdataset->rdclass);
235
0
  return ISC_R_SUCCESS;
236
0
}
237
238
static void
239
towire_addttl(dns_rdataset_t *rdataset, isc_buffer_t *target,
240
0
        isc_buffer_t *rdlen) {
241
0
  isc_buffer_putuint32(target, rdataset->ttl);
242
243
  /* Save space for rdlen. */
244
0
  *rdlen = *target;
245
0
  isc_buffer_add(target, 2);
246
0
}
247
248
static isc_result_t
249
towire_addrdata(dns_rdata_t *rdata, dns_compress_t *cctx, isc_buffer_t *target,
250
0
    isc_buffer_t *rdlen) {
251
0
  isc_result_t result = dns_rdata_towire(rdata, cctx, target);
252
0
  if (result != ISC_R_SUCCESS) {
253
0
    return result;
254
0
  }
255
0
  INSIST((target->used >= rdlen->used + 2) &&
256
0
         (target->used - rdlen->used - 2 < 65536));
257
0
  isc_buffer_putuint16(rdlen, (uint16_t)(target->used - rdlen->used - 2));
258
0
  return ISC_R_SUCCESS;
259
0
}
260
261
static isc_result_t
262
towire_question(dns_rdataset_t *rdataset, const dns_name_t *name,
263
    dns_compress_t *cctx, isc_buffer_t *target,
264
0
    isc_buffer_t *rrbuffer, unsigned int *countp) {
265
0
  isc_result_t result;
266
267
0
  result = dns_rdataset_first(rdataset);
268
0
  INSIST(result == ISC_R_NOMORE);
269
270
0
  result = towire_addtypeclass(rdataset, name, cctx, target, rrbuffer, 0);
271
0
  if (result != ISC_R_SUCCESS) {
272
0
    return ISC_R_SUCCESS;
273
0
  }
274
275
0
  *countp += 1;
276
277
0
  return ISC_R_SUCCESS;
278
0
}
279
280
static isc_result_t
281
towire_answer(dns_rdataset_t *rdataset, const dns_name_t *name,
282
        dns_compress_t *cctx, isc_buffer_t *target,
283
0
        isc_buffer_t *rrbuffer, uint16_t id, unsigned int *countp) {
284
0
  isc_result_t result;
285
0
  size_t start = 0, count = 0, added = 0;
286
0
  isc_buffer_t rdlen;
287
0
  dns_rdata_t *rdatas = dns__rdataset_rdatas;
288
289
0
  count = dns_rdataset_count(rdataset);
290
0
  result = dns_rdataset_first(rdataset);
291
0
  if (result == ISC_R_NOMORE) {
292
0
    return ISC_R_SUCCESS;
293
0
  } else if (result != ISC_R_SUCCESS) {
294
0
    return result;
295
0
  }
296
297
0
  if (WANT_CYCLIC(rdataset) && rdataset->type != dns_rdatatype_rrsig) {
298
0
    start = id % count;
299
300
    /* Do we need larger buffer? */
301
0
    if (start > ARRAY_SIZE(dns__rdataset_rdatas)) {
302
0
      rdatas = isc_mem_cget(cctx->mctx, start,
303
0
                sizeof(rdatas[0]));
304
0
    }
305
0
  }
306
307
  /*
308
   * Save the rdata up until the start.  If we are not
309
   * doing cyclic, the start == 0, so this is no-op.
310
   */
311
0
  for (size_t i = 0; i < start; i++) {
312
0
    dns_rdata_init(&rdatas[i]);
313
0
    dns_rdataset_current(rdataset, &rdatas[i]);
314
315
0
    result = dns_rdataset_next(rdataset);
316
0
    if (result == ISC_R_NOMORE) {
317
0
      result = ISC_R_SUCCESS;
318
0
      break;
319
0
    } else if (result != ISC_R_SUCCESS) {
320
0
      goto cleanup;
321
0
    }
322
0
  }
323
324
0
  for (size_t i = start; i < count; i++) {
325
0
    dns_rdata_t rdata = DNS_RDATA_INIT;
326
327
0
    result = towire_addtypeclass(rdataset, name, cctx, target,
328
0
               rrbuffer, sizeof(dns_ttl_t) + 2);
329
0
    if (result != ISC_R_SUCCESS) {
330
0
      goto cleanup;
331
0
    }
332
0
    towire_addttl(rdataset, target, &rdlen);
333
334
0
    dns_rdataset_current(rdataset, &rdata);
335
0
    result = towire_addrdata(&rdata, cctx, target, &rdlen);
336
0
    if (result != ISC_R_SUCCESS) {
337
0
      goto cleanup;
338
0
    }
339
0
    added++;
340
341
0
    result = dns_rdataset_next(rdataset);
342
0
    if (result == ISC_R_NOMORE) {
343
0
      result = ISC_R_SUCCESS;
344
0
      break;
345
0
    } else if (result != ISC_R_SUCCESS) {
346
0
      goto cleanup;
347
0
    }
348
0
  }
349
350
0
  for (size_t i = 0; i < start; i++) {
351
0
    result = towire_addtypeclass(rdataset, name, cctx, target,
352
0
               rrbuffer, sizeof(dns_ttl_t) + 2);
353
0
    if (result != ISC_R_SUCCESS) {
354
0
      goto cleanup;
355
0
    }
356
0
    towire_addttl(rdataset, target, &rdlen);
357
358
0
    result = towire_addrdata(&rdatas[i], cctx, target, &rdlen);
359
0
    if (result != ISC_R_SUCCESS) {
360
0
      goto cleanup;
361
0
    }
362
0
    added++;
363
0
  }
364
365
0
  INSIST(added == count);
366
367
0
cleanup:
368
0
  *countp += added;
369
0
  if (rdatas != dns__rdataset_rdatas) {
370
0
    isc_mem_cput(cctx->mctx, rdatas, start, sizeof(rdatas[0]));
371
0
  }
372
373
0
  return result;
374
0
}
375
376
isc_result_t
377
dns_rdataset_towire(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
378
        uint16_t id, dns_compress_t *cctx, isc_buffer_t *target,
379
0
        bool partial, unsigned int options, unsigned int *countp) {
380
0
  isc_result_t result;
381
0
  isc_buffer_t savedbuffer = *target;
382
0
  isc_buffer_t rrbuffer;
383
0
  dns_fixedname_t fixed;
384
0
  dns_name_t *name = NULL;
385
386
  /*
387
   * Convert 'rdataset' to wire format, compressing names as specified
388
   * in cctx, and storing the result in 'target'.
389
   */
390
391
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
392
0
  REQUIRE(rdataset->methods != NULL);
393
0
  REQUIRE(countp != NULL);
394
0
  REQUIRE(cctx != NULL && cctx->mctx != NULL);
395
396
0
  if (rdataset->attributes.negative) {
397
    /*
398
     * This is a negative caching rdataset.
399
     */
400
0
    unsigned int ncache_opts = 0;
401
0
    if ((options & DNS_RDATASETTOWIRE_OMITDNSSEC) != 0) {
402
0
      ncache_opts |= DNS_NCACHETOWIRE_OMITDNSSEC;
403
0
    }
404
0
    return dns_ncache_towire(rdataset, cctx, target, ncache_opts,
405
0
           countp);
406
0
  }
407
408
0
  name = dns_fixedname_initname(&fixed);
409
0
  dns_name_copy(owner_name, name);
410
0
  dns_rdataset_getownercase(rdataset, name);
411
0
  dns_compress_setmultiuse(cctx, true);
412
413
0
  name->attributes.nocompress |= owner_name->attributes.nocompress;
414
415
0
  if (rdataset->attributes.question) {
416
0
    result = towire_question(rdataset, name, cctx, target,
417
0
           &rrbuffer, countp);
418
0
    if (result != ISC_R_SUCCESS) {
419
0
      goto rollback;
420
0
    }
421
0
  } else {
422
0
    result = towire_answer(rdataset, name, cctx, target, &rrbuffer,
423
0
               id, countp);
424
0
    if (result != ISC_R_SUCCESS) {
425
0
      goto rollback;
426
0
    }
427
0
  }
428
429
0
  return ISC_R_SUCCESS;
430
431
0
rollback:
432
0
  if (partial && result == ISC_R_NOSPACE) {
433
0
    dns_compress_rollback(cctx, rrbuffer.used);
434
0
    *target = rrbuffer;
435
0
    return result;
436
0
  }
437
0
  dns_compress_rollback(cctx, savedbuffer.used);
438
0
  *countp = 0;
439
0
  *target = savedbuffer;
440
441
0
  return result;
442
0
}
443
444
isc_result_t
445
dns_rdataset_additionaldata(dns_rdataset_t *rdataset,
446
          const dns_name_t *owner_name,
447
          dns_additionaldatafunc_t add, void *arg,
448
0
          size_t limit) {
449
  /*
450
   * For each rdata in rdataset, call 'add' for each name and type in the
451
   * rdata which is subject to additional section processing.
452
   */
453
454
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
455
0
  REQUIRE(!rdataset->attributes.question);
456
457
0
  if (limit != 0 && dns_rdataset_count(rdataset) > limit) {
458
0
    return DNS_R_TOOMANYRECORDS;
459
0
  }
460
461
0
  DNS_RDATASET_FOREACH(rdataset) {
462
0
    isc_result_t result;
463
0
    dns_rdata_t rdata = DNS_RDATA_INIT;
464
0
    dns_rdataset_current(rdataset, &rdata);
465
0
    result = dns_rdata_additionaldata(&rdata, owner_name, add, arg);
466
0
    if (result != ISC_R_SUCCESS) {
467
0
      return result;
468
0
    }
469
0
  }
470
471
0
  return ISC_R_SUCCESS;
472
0
}
473
474
isc_result_t
475
0
dns_rdataset_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name) {
476
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
477
0
  REQUIRE(rdataset->methods != NULL);
478
0
  if (rdataset->methods->addnoqname == NULL) {
479
0
    return ISC_R_NOTIMPLEMENTED;
480
0
  }
481
0
  return (rdataset->methods->addnoqname)(rdataset, name);
482
0
}
483
484
isc_result_t
485
dns__rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
486
       dns_rdataset_t *neg,
487
0
       dns_rdataset_t *negsig DNS__DB_FLARG) {
488
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
489
0
  REQUIRE(rdataset->methods != NULL);
490
491
0
  if (rdataset->methods->getnoqname == NULL) {
492
0
    return ISC_R_NOTIMPLEMENTED;
493
0
  }
494
0
  return (rdataset->methods->getnoqname)(rdataset, name, neg,
495
0
                 negsig DNS__DB_FLARG_PASS);
496
0
}
497
498
isc_result_t
499
0
dns_rdataset_addclosest(dns_rdataset_t *rdataset, dns_name_t *name) {
500
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
501
0
  REQUIRE(rdataset->methods != NULL);
502
0
  if (rdataset->methods->addclosest == NULL) {
503
0
    return ISC_R_NOTIMPLEMENTED;
504
0
  }
505
0
  return (rdataset->methods->addclosest)(rdataset, name);
506
0
}
507
508
isc_result_t
509
dns__rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name,
510
       dns_rdataset_t *neg,
511
0
       dns_rdataset_t *negsig DNS__DB_FLARG) {
512
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
513
0
  REQUIRE(rdataset->methods != NULL);
514
515
0
  if (rdataset->methods->getclosest == NULL) {
516
0
    return ISC_R_NOTIMPLEMENTED;
517
0
  }
518
0
  return (rdataset->methods->getclosest)(rdataset, name, neg,
519
0
                 negsig DNS__DB_FLARG_PASS);
520
0
}
521
522
void
523
0
dns_rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust) {
524
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
525
0
  REQUIRE(rdataset->methods != NULL);
526
527
0
  if (rdataset->methods->settrust != NULL) {
528
0
    (rdataset->methods->settrust)(rdataset, trust);
529
0
  } else {
530
0
    rdataset->trust = trust;
531
0
  }
532
0
}
533
534
void
535
0
dns__rdataset_expire(dns_rdataset_t *rdataset DNS__DB_FLARG) {
536
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
537
0
  REQUIRE(rdataset->methods != NULL);
538
539
0
  if (rdataset->methods->expire != NULL) {
540
0
    (rdataset->methods->expire)(rdataset DNS__DB_FLARG_PASS);
541
0
  }
542
0
}
543
544
void
545
0
dns_rdataset_clearprefetch(dns_rdataset_t *rdataset) {
546
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
547
0
  REQUIRE(rdataset->methods != NULL);
548
549
0
  if (rdataset->methods->clearprefetch != NULL) {
550
0
    (rdataset->methods->clearprefetch)(rdataset);
551
0
  }
552
0
}
553
554
void
555
0
dns_rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) {
556
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
557
0
  REQUIRE(rdataset->methods != NULL);
558
559
0
  if (rdataset->methods->setownercase != NULL &&
560
0
      !rdataset->attributes.keepcase)
561
0
  {
562
0
    (rdataset->methods->setownercase)(rdataset, name);
563
0
  }
564
0
}
565
566
void
567
0
dns_rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name) {
568
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
569
0
  REQUIRE(rdataset->methods != NULL);
570
571
0
  if (rdataset->methods->getownercase != NULL &&
572
0
      !rdataset->attributes.keepcase)
573
0
  {
574
0
    (rdataset->methods->getownercase)(rdataset, name);
575
0
  }
576
0
}
577
578
void
579
dns_rdataset_trimttl(dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
580
         dns_rdata_rrsig_t *rrsig, isc_stdtime_t now,
581
0
         bool acceptexpired) {
582
0
  uint32_t ttl = 0;
583
584
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
585
0
  REQUIRE(DNS_RDATASET_VALID(sigrdataset));
586
0
  REQUIRE(rrsig != NULL);
587
588
  /*
589
   * If we accept expired RRsets keep them for no more than 120 seconds.
590
   */
591
0
  if (acceptexpired &&
592
0
      (isc_serial_le(rrsig->timeexpire, (now + 120) & 0xffffffff) ||
593
0
       isc_serial_le(rrsig->timeexpire, now)))
594
0
  {
595
0
    ttl = 120;
596
0
  } else if (isc_serial_ge(rrsig->timeexpire, now)) {
597
0
    ttl = rrsig->timeexpire - now;
598
0
  }
599
600
0
  ttl = ISC_MIN(ISC_MIN(rdataset->ttl, sigrdataset->ttl),
601
0
          ISC_MIN(rrsig->originalttl, ttl));
602
0
  rdataset->ttl = ttl;
603
0
  sigrdataset->ttl = ttl;
604
0
}
605
606
dns_slabheader_t *
607
0
dns_rdataset_getheader(const dns_rdataset_t *rdataset) {
608
0
  REQUIRE(DNS_RDATASET_VALID(rdataset));
609
610
0
  if (rdataset->methods->getheader != NULL) {
611
0
    return (rdataset->methods->getheader)(rdataset);
612
0
  }
613
614
0
  return NULL;
615
0
}