From: Colin Vidal Date: Mon, 31 Mar 2025 13:57:24 +0000 (+0200) Subject: add support for synthesized PTR answers X-Git-Tag: v9.21.14~13^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a0da7849930681ff2cb674716b762015002bc117;p=thirdparty%2Fbind9.git add support for synthesized PTR answers Add a BIND9 plugin which, in "reverse" mode, enables the server to build a synthesized response to a PTR query when the PTR record requested is not found in the zone. (The plugin won't be called for names below a delegation point, because it couldn't know whether a name actually exists within the delegation.) The dynamically-built name is constructed from a static prefix (passed as a plugin parameter), the IP address (extracted from the query name) and a suffx (also passed as a plugin parameter). An "allow-synth" address-match list is used to limit the network addresses for which the plugin may generate responses. The plugin can also be used in "forward" mode, to build synthesized A/AAAA records from names using the same format as he dynamically-built PTR names, if the query name and type are not found in the zone. The same parameters are used when the plugin is in forward mode: the plugin will react and answer a query if the name matches the configured prefix and origin, and encodes an IP address that is within "allow-synth". --- diff --git a/bin/plugins/meson.build b/bin/plugins/meson.build index 49114689f00..bb6679cf214 100644 --- a/bin/plugins/meson.build +++ b/bin/plugins/meson.build @@ -11,6 +11,7 @@ filter_a_src += files('filter-a.c') filter_aaaa_src += files('filter-aaaa.c') +synthrecord_src += files('synthrecord.c') manrst_srcset.add( files( diff --git a/bin/plugins/synthrecord.c b/bin/plugins/synthrecord.c new file mode 100644 index 00000000000..a42b4475811 --- /dev/null +++ b/bin/plugins/synthrecord.c @@ -0,0 +1,704 @@ +/* + * 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. + */ + +#include +#include +#include + +#include +#include +#include + +#include + +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) { \ + goto cleanup; \ + } \ + } while (0) + +#define DEFAULT_TTL 300 + +typedef enum { UNDEFINED, FORWARD, REVERSE } synthrecord_mode_t; + +typedef struct synthrecord synthrecord_t; +struct synthrecord { + isc_mem_t *mctx; + dns_acl_t *allowedsynth; + isc_region_t prefix; + dns_name_t origin; + uint32_t ttl; + synthrecord_mode_t mode; +}; + +static bool +synthrecord_allowedsynth(synthrecord_t *inst, isc_netaddr_t *net) { + return dns_acl_allowed(net, NULL, inst->allowedsynth, NULL); +} + +static void +synthrecord_chrreplace(isc_buffer_t *b, char from, char to) { + while (isc_buffer_consumedlength(b) < isc_buffer_usedlength(b)) { + char *c = isc_buffer_current(b); + + if (*c == from) { + *c = to; + } + isc_buffer_forward(b, 1); + } + + isc_buffer_first(b); +} + +static isc_result_t +synthrecord_reverseanswer(synthrecord_t *inst, isc_netaddr_t *na, + dns_name_t *synthname) { + isc_buffer_t b; + char bdata[DNS_NAME_FORMATSIZE]; + isc_buffer_t addrb; + char addrbdata[DNS_NAME_FORMATSIZE]; + isc_region_t addrr; + + REQUIRE(DNS_NAME_VALID(synthname)); + REQUIRE(na->family == AF_INET || na->family == AF_INET6); + + isc_buffer_init(&b, bdata, sizeof(bdata)); + isc_buffer_copyregion(&b, &inst->prefix); + + isc_buffer_init(&addrb, addrbdata, sizeof(addrbdata)); + isc_netaddr_totext(na, &addrb); + + /* + * IDN compatibility, as an IPv6 begining or ending with `::` will be + * converted into `--` and RFC5890 section 2.3.1 states that an IDN + * label can't start or end with an hyphen. + */ + if (na->family == AF_INET6) { + uint8_t c = 0; + + /* + * Address starts with `::`, so append a `0` right after the + * prefix. + */ + isc_buffer_peekuint8(&addrb, &c); + if (c == ':') { + isc_buffer_putuint8(&b, '0'); + } + + /* + * Address ends with `::`, so add a `0` at the end of the + * address. + */ + isc_buffer_forward(&addrb, isc_buffer_usedlength(&addrb) - 1); + isc_buffer_peekuint8(&addrb, &c); + if (c == ':') { + isc_buffer_putuint8(&addrb, '0'); + } + } + + isc_buffer_usedregion(&addrb, &addrr); + isc_buffer_copyregion(&b, &addrr); + + /* + * Do not attempt to replace anything in the prefix + */ + isc_buffer_forward(&b, inst->prefix.length); + synthrecord_chrreplace(&b, na->family == AF_INET ? '.' : ':', '-'); + + return dns_name_fromtext(synthname, &b, &inst->origin, 0); +} + +static isc_result_t +synthrecord_respond(synthrecord_t *inst, query_ctx_t *qctx, void *rdata, + dns_rdatatype_t rtype) { + isc_result_t result; + isc_mem_t *mctx = qctx->client->inner.view->mctx; + dns_message_t *msg = qctx->client->message; + dns_name_t aname = DNS_NAME_INITEMPTY; + dns_rdataset_t *synthset = NULL; + dns_rdatalist_t *synthlist = NULL; + dns_rdata_t *synthdata = NULL; + isc_buffer_t synthdatab; + char synthdatabdata[DNS_NAME_MAXWIRE]; + + /* + * Build the rdata from synthesized name + */ + dns_message_gettemprdata(msg, &synthdata); + isc_buffer_init(&synthdatab, synthdatabdata, sizeof(synthdatabdata)); + CHECK(dns_rdata_fromstruct(synthdata, dns_rdataclass_in, rtype, rdata, + &synthdatab)); + + /* + * Reference synthdata from the rdatalist + */ + dns_message_gettemprdatalist(msg, &synthlist); + synthlist->ttl = inst->ttl; + synthlist->rdclass = dns_rdataclass_in; + synthlist->type = rtype; + ISC_LIST_APPEND(synthlist->rdata, synthdata, link); + + /* + * Fill the rdataset with the rdatalist + */ + dns_message_gettemprdataset(msg, &synthset); + dns_rdatalist_tordataset(synthlist, synthset); + + /* + * Then create the name in the ANSWER section and attach the + * rdataset to it. + */ + dns_name_dup(qctx->client->query.qname, mctx, &aname); + dns_message_addname(msg, &aname, DNS_SECTION_ANSWER); + dns_rdataset_setownercase(synthset, &aname); + ISC_LIST_APPEND(aname.list, synthset, link); + + /* + * Send the message with the ANSWER section containing the + * synthesized PTR rdata. + */ + result = ns_query_done(qctx); + + /* + * Message is gone now, let's free message response datastructures + */ + dns_message_removename(msg, &aname, DNS_SECTION_ANSWER); + ISC_LIST_UNLINK(aname.list, synthset, link); + dns_name_free(&aname, mctx); + + dns_rdataset_disassociate(synthset); + dns_message_puttemprdataset(msg, &synthset); + + ISC_LIST_UNLINK(synthlist->rdata, synthdata, link); + dns_message_puttemprdatalist(msg, &synthlist); + +cleanup: + dns_message_puttemprdata(msg, &synthdata); + + return result; +} + +static bool +synthrecord_parseforward(synthrecord_t *inst, const dns_name_t *name, + isc_netaddr_t *addr) { + dns_name_t label; + char bdata[DNS_NAME_FORMATSIZE]; + isc_buffer_t b; + size_t labelcount = dns_name_countlabels(name); + dns_name_t subname; + + /* + * A forward name last label is `prefix-`. + */ + if (labelcount <= 2) { + return false; + } + + dns_name_init(&subname); + dns_name_getlabelsequence(name, 1, labelcount - 1, &subname); + if (!dns_name_equal(&subname, &inst->origin)) { + return false; + } + + /* + * First, extract the first label which contains the prefix (which + * should match) and the encoded address. + */ + dns_name_init(&label); + dns_name_getlabelsequence(name, 0, 1, &label); + dns_name_downcase(&label, &label); + + isc_buffer_init(&b, bdata, sizeof(bdata)); + dns_name_totext(&label, DNS_NAME_OMITFINALDOT, &b); + isc_buffer_putuint8(&b, 0); + if (strncmp((const char *)inst->prefix.base, isc_buffer_base(&b), + inst->prefix.length) != 0) + { + return false; + } + + /* + * Let's parse the address, starting right after the prefix. First try + * as if it's an IPv6 address, and IPv4 in case of failure. + */ + synthrecord_chrreplace(&b, '-', ':'); + isc_buffer_forward(&b, inst->prefix.length); + addr->family = AF_INET6; + if (inet_pton(addr->family, isc_buffer_current(&b), &addr->type.in6) == + 1) + { + return true; + } + + synthrecord_chrreplace(&b, ':', '.'); + isc_buffer_forward(&b, inst->prefix.length); + addr->family = AF_INET; + if (inet_pton(addr->family, isc_buffer_current(&b), &addr->type.in) == + 1) + { + return true; + } + + return false; +} + +static ns_hookresult_t +synthrecord_forward(synthrecord_t *inst, query_ctx_t *qctx, + isc_result_t *resp) { + isc_netaddr_t addr; + const dns_name_t *qname = qctx->client->query.qname; + + *resp = ISC_R_UNSET; + + if (!synthrecord_parseforward(inst, qname, &addr)) { + return NS_HOOK_CONTINUE; + } + + if (!synthrecord_allowedsynth(inst, &addr)) { + return NS_HOOK_CONTINUE; + } + + if (qctx->qtype != dns_rdatatype_a && + qctx->qtype != dns_rdatatype_aaaa && + qctx->qtype != dns_rdatatype_any) + { + /* + * The name is a candidate for a synthetic record, but the type + * is not A/AAAA. So, from protocol perspective, a record with + * this name "exists", even if there is no answer here. + */ + qctx->client->message->rcode = dns_rcode_noerror; + *resp = ns_query_done(qctx); + return NS_HOOK_RETURN; + } + + if ((qctx->qtype == dns_rdatatype_a || + qctx->qtype == dns_rdatatype_any) && + addr.family == AF_INET) + { + dns_rdata_in_a_t ardata = { .in_addr = addr.type.in }; + DNS_RDATACOMMON_INIT(&ardata, dns_rdatatype_a, + dns_rdataclass_in); + *resp = synthrecord_respond(inst, qctx, &ardata, + dns_rdatatype_a); + } else if ((qctx->qtype == dns_rdatatype_aaaa || + qctx->qtype == dns_rdatatype_any) && + addr.family == AF_INET6) + { + dns_rdata_in_aaaa_t aaaardata = { .in6_addr = addr.type.in6 }; + DNS_RDATACOMMON_INIT(&aaaardata, dns_rdatatype_aaaa, + dns_rdataclass_in); + *resp = synthrecord_respond(inst, qctx, &aaaardata, + dns_rdatatype_aaaa); + } else { + /* + * qtype is A but the address format matches AAAA, or + * qtype AAAA but format A. Either way, there is nothing + * to answer here. + */ + qctx->client->message->rcode = dns_rcode_noerror; + *resp = ns_query_done(qctx); + } + + return NS_HOOK_RETURN; +} + +static ns_hookresult_t +synthrecord_reverse(synthrecord_t *inst, query_ctx_t *qctx, + isc_result_t *resp) { + isc_result_t result; + dns_name_t aname = DNS_NAME_INITEMPTY; + char anamebdata[DNS_NAME_FORMATSIZE]; + isc_buffer_t anameb; + isc_netaddr_t qaddr; + const dns_name_t *qname = qctx->client->query.qname; + dns_rdata_ptr_t synthptrdata; + + *resp = ISC_R_UNSET; + + result = dns_byaddr_parseptrname(qname, &qaddr); + if (result != ISC_R_SUCCESS) { + isc_log_write(NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_DEBUG(10), + "synthrecord ptr parsing error %s", + isc_result_totext(result)); + return NS_HOOK_CONTINUE; + } + + if (!synthrecord_allowedsynth(inst, &qaddr)) { + return NS_HOOK_CONTINUE; + } + + if (qctx->qtype != dns_rdatatype_ptr && + qctx->qtype != dns_rdatatype_any) + { + /* + * The name is a candidate for a synthetic record, but the + * type is not PTR. So, from protocol perspective, a record + * with this name "exists", even if there is no answer + * here. + */ + qctx->client->message->rcode = dns_rcode_noerror; + *resp = ns_query_done(qctx); + return NS_HOOK_RETURN; + } + + isc_buffer_init(&anameb, anamebdata, sizeof(anamebdata)); + dns_name_setbuffer(&aname, &anameb); + result = synthrecord_reverseanswer(inst, &qaddr, &aname); + if (result != ISC_R_SUCCESS) { + isc_log_write( + NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_DEBUG(1), + "synthrecord cannot create reverse answer name: %s", + isc_result_totext(result)); + return NS_HOOK_CONTINUE; + } + + synthptrdata = (dns_rdata_ptr_t){ + .mctx = qctx->client->inner.view->mctx, .ptr = aname + }; + DNS_RDATACOMMON_INIT(&synthptrdata, dns_rdatatype_ptr, + dns_rdataclass_in); + result = synthrecord_respond(inst, qctx, &synthptrdata, + dns_rdatatype_ptr); + *resp = result; + + return NS_HOOK_RETURN; +} + +static ns_hookresult_t +synthrecord_entry(void *arg, void *cbdata, isc_result_t *resp) { + synthrecord_t *inst = cbdata; + query_ctx_t *qctx = arg; + + REQUIRE(qctx != NULL && qctx->zone != NULL); + REQUIRE(inst != NULL); + + switch (inst->mode) { + case FORWARD: + return synthrecord_forward(inst, qctx, resp); + case REVERSE: + return synthrecord_reverse(inst, qctx, resp); + default: + REQUIRE(false); + } +} + +static cfg_clausedef_t synthrecord_cfgclauses[] = { + { "prefix", &cfg_type_astring, 0 }, + { "origin", &cfg_type_astring, 0 }, + { "allow-synth", &cfg_type_bracketed_aml, 0 }, + { "ttl", &cfg_type_uint32, 0 }, + { "mode", &cfg_type_ustring, 0 } +}; + +static cfg_clausedef_t *synthrecord_cfgparamsclausesets[] = { + synthrecord_cfgclauses, NULL +}; + +static cfg_type_t synthrecord_cfgparams = { + "synthrecord-params", cfg_parse_mapbody, cfg_print_mapbody, + cfg_doc_mapbody, &cfg_rep_map, synthrecord_cfgparamsclausesets +}; + +static isc_result_t +synthrecord_initprefix(synthrecord_t *inst, const cfg_obj_t *synthrecordcfg) { + isc_result_t result; + size_t len; + const char *base = NULL; + const cfg_obj_t *obj = NULL; + + result = cfg_map_get(synthrecordcfg, "prefix", &obj); + if (result != ISC_R_SUCCESS) { + return result; + } + + len = obj->value.string.length; + base = obj->value.string.base; + + if (strstr(base, ".") != NULL) { + isc_log_write(NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_ERROR, + "synthrecord: prefix '%s' must be a single label", + base); + return ISC_R_UNEXPECTEDTOKEN; + } + + inst->prefix = (isc_region_t){ + .base = isc_mem_allocate(inst->mctx, len), .length = len + }; + memmove(inst->prefix.base, base, len); + + /* + * Avoid dynamically lower-casing the prefix when parsing the + * address in the forward flow. + */ + isc_ascii_lowercopy((uint8_t *)inst->prefix.base, + (uint8_t *)inst->prefix.base, inst->prefix.length); + + return result; +} + +static isc_result_t +synthrecord_initorigin(synthrecord_t *inst, const cfg_obj_t *synthrecordcfg) { + isc_result_t result; + const cfg_obj_t *obj = NULL; + const char *originstr = NULL; + + result = cfg_map_get(synthrecordcfg, "origin", &obj); + if (result != ISC_R_SUCCESS) { + return result; + } + + originstr = cfg_obj_asstring(obj); + dns_name_init(&inst->origin); + result = dns_name_fromstring(&inst->origin, originstr, NULL, 0, + inst->mctx); + if (result != ISC_R_SUCCESS) { + return result; + } + + if (!dns_name_isabsolute(&inst->origin)) { + isc_log_write(NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_ERROR, + "synthrecord: origin '%s' is not absolute", + originstr); + return ISC_R_FAILURE; + } + + return result; +} + +static isc_result_t +synthrecord_parseconfigmode(synthrecord_t *inst, + const cfg_obj_t *synthrecordcfg) { + isc_result_t result; + const cfg_obj_t *obj = NULL; + const char *modestr = NULL; + + result = cfg_map_get(synthrecordcfg, "mode", &obj); + if (result != ISC_R_SUCCESS) { + isc_log_write(NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_ERROR, + "synthrecord: missing mode (forward or reverse)"); + return result; + } + + modestr = obj->value.string.base; + if (strcasecmp("forward", modestr) == 0) { + inst->mode = FORWARD; + } else if (strcasecmp("reverse", modestr) == 0) { + inst->mode = REVERSE; + } else { + isc_log_write(NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_ERROR, + "synthrecord: mode %s is not allowed (forward or " + "reverse only)", + modestr); + result = ISC_R_NOTFOUND; + } + + return result; +} + +static isc_result_t +synthrecord_parseallowsynth(synthrecord_t *inst, const cfg_obj_t *cfg, + cfg_aclconfctx_t *aclctx, + const cfg_obj_t *synthrecordcfg) { + isc_result_t result; + const cfg_obj_t *obj = NULL; + + INSIST(inst->allowedsynth == NULL); + result = cfg_map_get(synthrecordcfg, "allow-synth", &obj); + + if (result == ISC_R_NOTFOUND) { + return dns_acl_any(inst->mctx, &inst->allowedsynth); + } + + if (result != ISC_R_SUCCESS) { + return result; + } + + result = cfg_acl_fromconfig(obj, cfg, aclctx, inst->mctx, 0, + &inst->allowedsynth); + if (result != ISC_R_SUCCESS) { + return result; + } + + for (unsigned int i = 0; i < inst->allowedsynth->length; i++) { + switch (inst->allowedsynth->elements[i].type) { + case dns_aclelementtype_nestedacl: + case dns_aclelementtype_localhost: + case dns_aclelementtype_localnets: + continue; + default: + /* This rejects keyname and geoip elements */ + isc_log_write(NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, + "synthrecord: allow-synth must be an " + "address-match list"); + return ISC_R_UNEXPECTED; + } + } + return result; +} + +static isc_result_t +synthrecord_parsettl(synthrecord_t *inst, const cfg_obj_t *synthrecordcfg) { + isc_result_t result; + const cfg_obj_t *obj = NULL; + + result = cfg_map_get(synthrecordcfg, "ttl", &obj); + + if (result == ISC_R_NOTFOUND) { + inst->ttl = DEFAULT_TTL; + result = ISC_R_SUCCESS; + } else if (result == ISC_R_SUCCESS) { + inst->ttl = cfg_obj_asuint32(obj); + } + + return result; +} + +static isc_result_t +synthrecord_parseconfig(synthrecord_t *inst, const char *parameters, + const cfg_obj_t *cfg, const char *cfgfile, + unsigned long cfgline, cfg_aclconfctx_t *aclctx) { + isc_result_t result; + isc_mem_t *mctx = inst->mctx; + cfg_parser_t *parser = NULL; + cfg_obj_t *synthrecordcfg = NULL; + isc_buffer_t b; + + CHECK(cfg_parser_create(mctx, &parser)); + + isc_buffer_constinit(&b, parameters, strlen(parameters)); + isc_buffer_add(&b, strlen(parameters)); + + CHECK(cfg_parse_buffer(parser, &b, cfgfile, cfgline, + &synthrecord_cfgparams, 0, &synthrecordcfg)); + + CHECK(synthrecord_parseconfigmode(inst, synthrecordcfg)); + CHECK(synthrecord_initorigin(inst, synthrecordcfg)); + CHECK(synthrecord_initprefix(inst, synthrecordcfg)); + CHECK(synthrecord_parseallowsynth(inst, cfg, aclctx, synthrecordcfg)); + CHECK(synthrecord_parsettl(inst, synthrecordcfg)); + +cleanup: + if (synthrecordcfg != NULL) { + cfg_obj_destroy(parser, &synthrecordcfg); + } + + if (parser != NULL) { + cfg_parser_destroy(&parser); + } + + return result; +} + +isc_result_t +plugin_register(const char *parameters, const void *cfg, const char *cfgfile, + unsigned long cfgline, isc_mem_t *mctx, void *actx, + ns_hooktable_t *hooktable, const ns_pluginregister_ctx_t *ctx, + void **instp) { + synthrecord_t *inst = NULL; + ns_hook_t hook; + isc_result_t result; + + REQUIRE(cfg); + REQUIRE(mctx); + REQUIRE(actx); + REQUIRE(hooktable); + REQUIRE(instp && *instp == NULL); + + if (ctx->source != NS_HOOKSOURCE_ZONE) { + isc_log_write(NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_INFO, + "registering 'synthrecord' failed as it was not " + "configured as a zone plugin"); + return ISC_R_FAILURE; + } + + isc_log_write(NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, ISC_LOG_INFO, + "registering 'synthrecord' module from %s:%lu", cfgfile, + cfgline); + + inst = isc_mem_get(mctx, sizeof(*inst)); + *inst = (synthrecord_t){ .prefix = {} }; + *instp = inst; + + isc_mem_attach(mctx, &inst->mctx); + result = ISC_R_SUCCESS; + result = synthrecord_parseconfig(inst, parameters, cfg, cfgfile, + cfgline, actx); + + hook = (ns_hook_t){ .action = synthrecord_entry, .action_data = inst }; + ns_hook_add(hooktable, mctx, NS_QUERY_NXDOMAIN_BEGIN, &hook); + + /* + * The qname with a different type might be defined in the zone. If + * there is a delegation, NS_QUERY_NODATA_BEGIN is never called. + */ + ns_hook_add(hooktable, mctx, NS_QUERY_NODATA_BEGIN, &hook); + + return result; +} + +isc_result_t +plugin_check(const char *parameters, const void *cfg, const char *cfgfile, + unsigned long cfgline, isc_mem_t *mctx, void *actx, + const ns_pluginregister_ctx_t *ctx ISC_ATTR_UNUSED) { + isc_result_t result; + synthrecord_t *inst; + + inst = isc_mem_get(mctx, sizeof(*inst)); + *inst = (synthrecord_t){}; + + isc_mem_attach(mctx, &inst->mctx); + result = synthrecord_parseconfig(inst, parameters, cfg, cfgfile, + cfgline, actx); + plugin_destroy((void **)&inst); + + return result; +} + +void +plugin_destroy(void **instp) { + REQUIRE(instp && *instp); + + synthrecord_t *inst = *instp; + isc_mem_t *mctx = inst->mctx; + + if (inst->allowedsynth != NULL) { + dns_acl_detach(&inst->allowedsynth); + } + + if (inst->prefix.base != NULL) { + isc_mem_free(mctx, inst->prefix.base); + } + + if (DNS_NAME_VALID(&inst->origin)) { + dns_name_free(&inst->origin, inst->mctx); + } + + isc_mem_putanddetach(&inst->mctx, inst, sizeof(*inst)); + *instp = NULL; +} + +int +plugin_version(void) { + return NS_PLUGIN_VERSION; +} diff --git a/meson.build b/meson.build index 972f41fed97..f614dfe4228 100644 --- a/meson.build +++ b/meson.build @@ -1074,6 +1074,7 @@ tsig_keygen_src = [] filter_a_src = [] filter_aaaa_src = [] +synthrecord_src = [] fuzz_binaries = {} system_test_binaries = {} @@ -1709,6 +1710,21 @@ shared_library( ], ) +shared_library( + 'synthrecord', + synthrecord_src, + implicit_include_directories: false, + install: true, + install_rpath: libdir, + install_dir: libdir / 'bind', + name_prefix: '', + dependencies: [ + libdns_dep, + libisccfg_dep, + libns_dep, + ], +) + subdir('doc') subdir('tests')