2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-22 10:10:06 +00:00
bind/lib/dns/rdataset.c
Petr Špaček 750d8a61b6 Convert DNS_RDATASETATTR_ bitfield manipulation to struct of bools
RRset ordering is now an enum inside struct rdataset attributes. This
was done to keep size to of the structure to its original value before
this MR.

I expect zero performance impact but it should be easier to deal with
attributes in debuggers and language servers.
2025-07-10 11:17:19 +02:00

660 lines
16 KiB
C

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
/*! \file */
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <isc/buffer.h>
#include <isc/mem.h>
#include <isc/random.h>
#include <isc/serial.h>
#include <isc/util.h>
#include <dns/compress.h>
#include <dns/fixedname.h>
#include <dns/name.h>
#include <dns/ncache.h>
#include <dns/rdata.h>
#include <dns/rdataset.h>
#include <dns/types.h>
static const char *trustnames[] = {
"none", "pending-additional",
"pending-answer", "additional",
"glue", "answer",
"authauthority", "authanswer",
"secure", "local" /* aka ultimate */
};
const char *
dns_trust_totext(dns_trust_t trust) {
if (trust >= sizeof(trustnames) / sizeof(*trustnames)) {
return "bad";
}
return trustnames[trust];
}
void
dns_rdataset_init(dns_rdataset_t *rdataset) {
/*
* Make 'rdataset' a valid, disassociated rdataset.
*/
REQUIRE(rdataset != NULL);
*rdataset = (dns_rdataset_t){
.magic = DNS_RDATASET_MAGIC,
.link = ISC_LINK_INITIALIZER,
.count = DNS_RDATASET_COUNT_UNDEFINED,
};
}
void
dns_rdataset_invalidate(dns_rdataset_t *rdataset) {
/*
* Invalidate 'rdataset'.
*/
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods == NULL);
*rdataset = (dns_rdataset_t){
.magic = 0,
.link = ISC_LINK_INITIALIZER,
.count = DNS_RDATASET_COUNT_UNDEFINED,
};
}
void
dns__rdataset_disassociate(dns_rdataset_t *rdataset DNS__DB_FLARG) {
/*
* Disassociate 'rdataset' from its rdata, allowing it to be reused.
*/
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
if (rdataset->methods->disassociate != NULL) {
(rdataset->methods->disassociate)(rdataset DNS__DB_FLARG_PASS);
}
*rdataset = (dns_rdataset_t){
.magic = DNS_RDATASET_MAGIC,
.link = ISC_LINK_INITIALIZER,
.count = DNS_RDATASET_COUNT_UNDEFINED,
};
}
bool
dns_rdataset_isassociated(dns_rdataset_t *rdataset) {
/*
* Is 'rdataset' associated?
*/
REQUIRE(DNS_RDATASET_VALID(rdataset));
if (rdataset->methods != NULL) {
return true;
}
return false;
}
static isc_result_t
question_cursor(dns_rdataset_t *rdataset ISC_ATTR_UNUSED) {
return ISC_R_NOMORE;
}
static void
question_clone(dns_rdataset_t *source, dns_rdataset_t *target DNS__DB_FLARG) {
*target = *source;
}
static dns_rdatasetmethods_t question_methods = {
.first = question_cursor,
.next = question_cursor,
.clone = question_clone,
};
void
dns_rdataset_makequestion(dns_rdataset_t *rdataset, dns_rdataclass_t rdclass,
dns_rdatatype_t type) {
/*
* Make 'rdataset' a valid, associated, question rdataset, with a
* question class of 'rdclass' and type 'type'.
*/
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods == NULL);
rdataset->methods = &question_methods;
rdataset->rdclass = rdclass;
rdataset->type = type;
rdataset->attributes.question = true;
}
unsigned int
dns_rdataset_count(dns_rdataset_t *rdataset) {
/*
* Return the number of records in 'rdataset'.
*/
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
REQUIRE(rdataset->methods->count != NULL);
return (rdataset->methods->count)(rdataset);
}
void
dns__rdataset_clone(dns_rdataset_t *source,
dns_rdataset_t *target DNS__DB_FLARG) {
/*
* Make 'target' refer to the same rdataset as 'source'.
*/
REQUIRE(DNS_RDATASET_VALID(source));
REQUIRE(source->methods != NULL);
REQUIRE(DNS_RDATASET_VALID(target));
REQUIRE(target->methods == NULL);
(source->methods->clone)(source, target DNS__DB_FLARG_PASS);
}
isc_result_t
dns_rdataset_first(dns_rdataset_t *rdataset) {
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
REQUIRE(rdataset->methods->first != NULL);
isc_result_t result = rdataset->methods->first(rdataset);
ENSURE(result == ISC_R_SUCCESS || result == ISC_R_NOMORE);
return result;
}
isc_result_t
dns_rdataset_next(dns_rdataset_t *rdataset) {
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
REQUIRE(rdataset->methods->next != NULL);
isc_result_t result = rdataset->methods->next(rdataset);
ENSURE(result == ISC_R_SUCCESS || result == ISC_R_NOMORE);
return result;
}
void
dns_rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
/*
* Make 'rdata' refer to the current rdata.
*/
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
REQUIRE(rdataset->methods->current != NULL);
(rdataset->methods->current)(rdataset, rdata);
}
#define MAX_SHUFFLE 32
#define WANT_RANDOM(r) (((r)->attributes.order == dns_order_randomize))
#define WANT_CYCLIC(r) (((r)->attributes.order == dns_order_cyclic))
struct towire_sort {
int key;
dns_rdata_t *rdata;
};
static void
swap_rdata(dns_rdata_t *in, unsigned int a, unsigned int b) {
dns_rdata_t rdata = in[a];
in[a] = in[b];
in[b] = rdata;
}
static isc_result_t
towire(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
dns_compress_t *cctx, isc_buffer_t *target, bool partial,
unsigned int options, unsigned int *countp,
void **state ISC_ATTR_UNUSED) {
isc_region_t r;
isc_result_t result;
unsigned int i, count = 0, added;
isc_buffer_t savedbuffer, rdlen, rrbuffer;
unsigned int headlen;
bool question = false;
bool shuffle = false;
bool want_random, want_cyclic;
dns_rdata_t in_fixed[MAX_SHUFFLE];
dns_rdata_t *in = in_fixed;
struct towire_sort out_fixed[MAX_SHUFFLE];
struct towire_sort *out = out_fixed;
dns_fixedname_t fixed;
dns_name_t *name = NULL;
/*
* Convert 'rdataset' to wire format, compressing names as specified
* in cctx, and storing the result in 'target'.
*/
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
REQUIRE(countp != NULL);
REQUIRE(cctx != NULL && cctx->mctx != NULL);
want_random = WANT_RANDOM(rdataset);
want_cyclic = WANT_CYCLIC(rdataset);
if (rdataset->attributes.question) {
question = true;
count = 1;
result = dns_rdataset_first(rdataset);
INSIST(result == ISC_R_NOMORE);
} else if (rdataset->attributes.negative) {
/*
* This is a negative caching rdataset.
*/
unsigned int ncache_opts = 0;
if ((options & DNS_RDATASETTOWIRE_OMITDNSSEC) != 0) {
ncache_opts |= DNS_NCACHETOWIRE_OMITDNSSEC;
}
return dns_ncache_towire(rdataset, cctx, target, ncache_opts,
countp);
} else {
count = dns_rdataset_count(rdataset);
result = dns_rdataset_first(rdataset);
if (result == ISC_R_NOMORE) {
return ISC_R_SUCCESS;
}
if (result != ISC_R_SUCCESS) {
return result;
}
}
/*
* Do we want to shuffle this answer?
*/
if (!question && count > 1 && rdataset->type != dns_rdatatype_rrsig) {
if (want_random || want_cyclic) {
shuffle = true;
}
}
if (shuffle) {
if (count > MAX_SHUFFLE) {
in = isc_mem_cget(cctx->mctx, count, sizeof(*in));
out = isc_mem_cget(cctx->mctx, count, sizeof(*out));
if (in == NULL || out == NULL) {
shuffle = false;
}
}
}
if (shuffle) {
uint32_t seed = 0;
unsigned int j = 0;
/*
* First we get handles to all of the rdata.
*/
i = 0;
do {
INSIST(i < count);
dns_rdata_init(&in[i]);
dns_rdataset_current(rdataset, &in[i]);
i++;
result = dns_rdataset_next(rdataset);
} while (result == ISC_R_SUCCESS);
if (result != ISC_R_NOMORE) {
goto cleanup;
}
INSIST(i == count);
if (want_random) {
seed = isc_random32();
}
if (want_cyclic &&
(rdataset->count != DNS_RDATASET_COUNT_UNDEFINED))
{
j = rdataset->count % count;
}
for (i = 0; i < count; i++) {
if (want_random) {
swap_rdata(in, j, j + seed % (count - j));
}
out[i].key = 0;
out[i].rdata = &in[j];
if (++j == count) {
j = 0;
}
}
}
savedbuffer = *target;
i = 0;
added = 0;
name = dns_fixedname_initname(&fixed);
dns_name_copy(owner_name, name);
dns_rdataset_getownercase(rdataset, name);
dns_compress_setmultiuse(cctx, true);
name->attributes.nocompress |= owner_name->attributes.nocompress;
do {
/*
* Copy out the name, type, class, ttl.
*/
rrbuffer = *target;
dns_compress_setpermitted(cctx, true);
result = dns_name_towire(name, cctx, target);
if (result != ISC_R_SUCCESS) {
goto rollback;
}
headlen = sizeof(dns_rdataclass_t) + sizeof(dns_rdatatype_t);
if (!question) {
headlen += sizeof(dns_ttl_t) + 2;
} /* XXX 2 for rdata len */
isc_buffer_availableregion(target, &r);
if (r.length < headlen) {
result = ISC_R_NOSPACE;
goto rollback;
}
isc_buffer_putuint16(target, rdataset->type);
isc_buffer_putuint16(target, rdataset->rdclass);
if (!question) {
dns_rdata_t rdata = DNS_RDATA_INIT;
isc_buffer_putuint32(target, rdataset->ttl);
/*
* Save space for rdlen.
*/
rdlen = *target;
isc_buffer_add(target, 2);
/*
* Copy out the rdata
*/
if (shuffle) {
rdata = *(out[i].rdata);
} else {
dns_rdata_reset(&rdata);
dns_rdataset_current(rdataset, &rdata);
}
result = dns_rdata_towire(&rdata, cctx, target);
if (result != ISC_R_SUCCESS) {
goto rollback;
}
INSIST((target->used >= rdlen.used + 2) &&
(target->used - rdlen.used - 2 < 65536));
isc_buffer_putuint16(
&rdlen,
(uint16_t)(target->used - rdlen.used - 2));
added++;
}
if (shuffle) {
i++;
if (i == count) {
result = ISC_R_NOMORE;
} else {
result = ISC_R_SUCCESS;
}
} else {
result = dns_rdataset_next(rdataset);
}
} while (result == ISC_R_SUCCESS);
if (result != ISC_R_NOMORE) {
goto rollback;
}
*countp += count;
result = ISC_R_SUCCESS;
goto cleanup;
rollback:
if (partial && result == ISC_R_NOSPACE) {
dns_compress_rollback(cctx, rrbuffer.used);
*countp += added;
*target = rrbuffer;
goto cleanup;
}
dns_compress_rollback(cctx, savedbuffer.used);
*countp = 0;
*target = savedbuffer;
cleanup:
if (out != NULL && out != out_fixed) {
isc_mem_cput(cctx->mctx, out, count, sizeof(*out));
}
if (in != NULL && in != in_fixed) {
isc_mem_cput(cctx->mctx, in, count, sizeof(*in));
}
return result;
}
isc_result_t
dns_rdataset_towirepartial(dns_rdataset_t *rdataset,
const dns_name_t *owner_name, dns_compress_t *cctx,
isc_buffer_t *target, unsigned int options,
unsigned int *countp, void **state) {
REQUIRE(state == NULL); /* XXX remove when implemented */
return towire(rdataset, owner_name, cctx, target, true, options, countp,
state);
}
isc_result_t
dns_rdataset_towire(dns_rdataset_t *rdataset, const dns_name_t *owner_name,
dns_compress_t *cctx, isc_buffer_t *target,
unsigned int options, unsigned int *countp) {
return towire(rdataset, owner_name, cctx, target, false, options,
countp, NULL);
}
isc_result_t
dns_rdataset_additionaldata(dns_rdataset_t *rdataset,
const dns_name_t *owner_name,
dns_additionaldatafunc_t add, void *arg,
size_t limit) {
/*
* For each rdata in rdataset, call 'add' for each name and type in the
* rdata which is subject to additional section processing.
*/
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(!rdataset->attributes.question);
if (limit != 0 && dns_rdataset_count(rdataset) > limit) {
return DNS_R_TOOMANYRECORDS;
}
DNS_RDATASET_FOREACH (rdataset) {
isc_result_t result;
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdataset_current(rdataset, &rdata);
result = dns_rdata_additionaldata(&rdata, owner_name, add, arg);
if (result != ISC_R_SUCCESS) {
return result;
}
}
return ISC_R_SUCCESS;
}
isc_result_t
dns_rdataset_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name) {
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
if (rdataset->methods->addnoqname == NULL) {
return ISC_R_NOTIMPLEMENTED;
}
return (rdataset->methods->addnoqname)(rdataset, name);
}
isc_result_t
dns__rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
dns_rdataset_t *neg,
dns_rdataset_t *negsig DNS__DB_FLARG) {
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
if (rdataset->methods->getnoqname == NULL) {
return ISC_R_NOTIMPLEMENTED;
}
return (rdataset->methods->getnoqname)(rdataset, name, neg,
negsig DNS__DB_FLARG_PASS);
}
isc_result_t
dns_rdataset_addclosest(dns_rdataset_t *rdataset, dns_name_t *name) {
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
if (rdataset->methods->addclosest == NULL) {
return ISC_R_NOTIMPLEMENTED;
}
return (rdataset->methods->addclosest)(rdataset, name);
}
isc_result_t
dns__rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name,
dns_rdataset_t *neg,
dns_rdataset_t *negsig DNS__DB_FLARG) {
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
if (rdataset->methods->getclosest == NULL) {
return ISC_R_NOTIMPLEMENTED;
}
return (rdataset->methods->getclosest)(rdataset, name, neg,
negsig DNS__DB_FLARG_PASS);
}
void
dns_rdataset_settrust(dns_rdataset_t *rdataset, dns_trust_t trust) {
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
if (rdataset->methods->settrust != NULL) {
(rdataset->methods->settrust)(rdataset, trust);
} else {
rdataset->trust = trust;
}
}
void
dns__rdataset_expire(dns_rdataset_t *rdataset DNS__DB_FLARG) {
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
if (rdataset->methods->expire != NULL) {
(rdataset->methods->expire)(rdataset DNS__DB_FLARG_PASS);
}
}
void
dns_rdataset_clearprefetch(dns_rdataset_t *rdataset) {
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
if (rdataset->methods->clearprefetch != NULL) {
(rdataset->methods->clearprefetch)(rdataset);
}
}
void
dns_rdataset_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) {
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
if (rdataset->methods->setownercase != NULL &&
!rdataset->attributes.keepcase)
{
(rdataset->methods->setownercase)(rdataset, name);
}
}
void
dns_rdataset_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name) {
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(rdataset->methods != NULL);
if (rdataset->methods->getownercase != NULL &&
!rdataset->attributes.keepcase)
{
(rdataset->methods->getownercase)(rdataset, name);
}
}
void
dns_rdataset_trimttl(dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
dns_rdata_rrsig_t *rrsig, isc_stdtime_t now,
bool acceptexpired) {
uint32_t ttl = 0;
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE(DNS_RDATASET_VALID(sigrdataset));
REQUIRE(rrsig != NULL);
/*
* If we accept expired RRsets keep them for no more than 120 seconds.
*/
if (acceptexpired &&
(isc_serial_le(rrsig->timeexpire, (now + 120) & 0xffffffff) ||
isc_serial_le(rrsig->timeexpire, now)))
{
ttl = 120;
} else if (isc_serial_ge(rrsig->timeexpire, now)) {
ttl = rrsig->timeexpire - now;
}
ttl = ISC_MIN(ISC_MIN(rdataset->ttl, sigrdataset->ttl),
ISC_MIN(rrsig->originalttl, ttl));
rdataset->ttl = ttl;
sigrdataset->ttl = ttl;
}
dns_slabheader_t *
dns_rdataset_getheader(const dns_rdataset_t *rdataset) {
REQUIRE(DNS_RDATASET_VALID(rdataset));
if (rdataset->methods->getheader != NULL) {
return (rdataset->methods->getheader)(rdataset);
}
return NULL;
}
bool
dns_rdataset_equals(const dns_rdataset_t *rdataset1,
const dns_rdataset_t *rdataset2) {
REQUIRE(DNS_RDATASET_VALID(rdataset1));
REQUIRE(DNS_RDATASET_VALID(rdataset2));
if (rdataset1->methods->equals != NULL &&
rdataset1->methods->equals == rdataset2->methods->equals)
{
return (rdataset1->methods->equals)(rdataset1, rdataset2);
}
return false;
}