new: usr: New "rndc showconf" command

The new `rndc showconf` command prints the running server configuration. There are three options:
- `rndc showconf -user` displays the user configuration (i.e., the contents of `named.conf`).
- `rndc showconf -builtin` displays the default settings, similar to `named -H`.
- `rndc showconf -effective` displays the effective configuration. This is the merged combination of the `-user` and `-builtin` configurations.

Closes #1075

Merge branch 'colin/effective-config-rndc' into 'main'

See merge request isc-projects/bind9!11123
This commit is contained in:
Colin Vidal 2025-10-29 23:49:58 +01:00
commit dad960025c
12 changed files with 592 additions and 578 deletions

View file

@ -276,6 +276,8 @@ named_control_docommand(isccc_sexpr_t *message, bool readonly,
result = named_server_dumpsecroots(named_g_server, lex, text);
} else if (command_compare(command, NAMED_COMMAND_SERVESTALE)) {
result = named_server_servestale(named_g_server, lex, text);
} else if (command_compare(command, NAMED_COMMAND_SHOWCONF)) {
result = named_server_showconf(named_g_server, lex, text);
} else if (command_compare(command, NAMED_COMMAND_SHOWZONE)) {
result = named_server_showzone(named_g_server, lex, text);
} else if (command_compare(command, NAMED_COMMAND_SIGNING)) {

View file

@ -61,6 +61,7 @@
#define NAMED_COMMAND_SCAN "scan"
#define NAMED_COMMAND_SECROOTS "secroots"
#define NAMED_COMMAND_SERVESTALE "serve-stale"
#define NAMED_COMMAND_SHOWCONF "showconf"
#define NAMED_COMMAND_SHOWZONE "showzone"
#define NAMED_COMMAND_SIGN "sign"
#define NAMED_COMMAND_SIGNING "signing"

View file

@ -110,6 +110,8 @@ struct named_server {
isc_signal_t *sigusr1;
cfg_aclconfctx_t *aclctx;
cfg_obj_t *userconfig;
cfg_obj_t *effectiveconfig;
};
#define NAMED_SERVER_MAGIC ISC_MAGIC('S', 'V', 'E', 'R')
@ -324,6 +326,13 @@ isc_result_t
named_server_showzone(named_server_t *server, isc_lex_t *lex,
isc_buffer_t **text);
/*%
* Show the full current user configuration.
*/
isc_result_t
named_server_showconf(named_server_t *server, isc_lex_t *lex,
isc_buffer_t **text);
/*%
* Lists the status of the signing records for a given zone.
*/

File diff suppressed because it is too large Load diff

View file

@ -545,6 +545,28 @@ Currently supported commands are:
answers is currently enabled or disabled. It also reports the values of
``stale-answer-ttl`` and ``max-stale-ttl``.
.. option:: showconf ( -user | -builtin | -effective )
This command prints the current running configuration for the server.
There are three modes:
- ``rndc showconf -user`` prints the contents of :iscman:`named.conf`,
as of the time the server was most recently configured.
- ``rndc showconf -builtin`` prints the built-in default configuration
settings (which may be overridden by :iscman:`named.conf`).
- ``rndc showconf -effective`` prints the effective configuration
of the server. This is the merged combination of the ``-user`` and
``-builtin`` configurations.
Note that this information is only set or updated when the server is
loaded or reconfigured. Properties that can be dynamically changed
during runtime, such as the active root trust anchors, the debugging
level (via ``rndc trace``), or DNSSEC validation (via ``rndc
valiation``), are shown as configured, not as currently set, and zones that
are dynamically added using ``rndc addzone`` are not visible.
See also :option:`rndc showzone`.
.. option:: showzone zone [class [view]]
This command prints the configuration of a running zone.

View file

@ -0,0 +1,21 @@
; 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.
$TTL 120
@ SOA ns.unsigned. hostmaster.ns.unsigned. ( 1 3600 1200 604800 60 )
@ NS ns
@ MX 10 mx
ns A 10.53.0.1
AAAA fd92:7065:b8e:ffff::1
a A 1.1.1.1
mx A 2.2.2.2

View file

@ -0,0 +1,39 @@
/*
* 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.
*/
options {
query-source address 10.53.0.1;
notify-source 10.53.0.1;
transfer-source 10.53.0.1;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.1; };
recursion no;
dnssec-validation no;
notify yes;
minimal-responses no;
allow-new-zones yes;
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
zone "example.com" {
type primary;
file "example.db";
};

View file

@ -0,0 +1,62 @@
# 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.
import dns
import isctest
def test_showconf(ns1):
# Basic testing of rndc showconf
msg = isctest.query.create("a.example.com", "A")
res = isctest.query.udp(msg, "10.53.0.1")
isctest.check.rcode(res, dns.rcode.NOERROR)
effectiveconfig = ns1.rndc("showconf -effective", log=False)
assert 'zone "example.com"' in effectiveconfig
assert 'view "_bind" chaos {' in effectiveconfig
# builtin-trust-anchors is non documented and internal clause only, it must
# not be visible.
assert "builtin-trust-anchors" not in effectiveconfig
# Dynamically added zones are not visible from the effectiveconfig
zonedata = '"added.example" { type primary; file "example.db"; };'
ns1.rndc(f"addzone {zonedata}", log=False)
msg = isctest.query.create("a.added.example", "A")
res = isctest.query.udp(msg, "10.53.0.1")
isctest.check.rcode(res, dns.rcode.NOERROR)
effectiveconfig = ns1.rndc("showconf -effective", log=False)
assert 'zone "added.example"' not in effectiveconfig
userconfig = ns1.rndc("showconf -user", log=False)
assert 'zone "example.com"' in userconfig
assert 'view "_bind" chaos {' not in userconfig
builtinconfig = ns1.rndc("showconf -builtin", log=False)
assert len(userconfig.split()) < len(builtinconfig.split())
assert len(builtinconfig.split()) < len(effectiveconfig.split())
# Errors handling
error_msg = ""
try:
ns1.rndc("showconf -idontexist", log=False)
except isctest.rndc.RNDCException as e:
error_msg = str(e)
assert error_msg == "rndc: 'showconf' failed: syntax error\n"
try:
ns1.rndc("showconf", log=False)
except isctest.rndc.RNDCException as e:
error_msg = str(e)
assert error_msg == "rndc: 'showconf' failed: unexpected end of input\n"

View file

@ -77,6 +77,13 @@
#include <dns/types.h>
#include <dns/zt.h>
/*
* These are opaque types that will be used by named to save
* newzones configuration in the view.
*/
typedef struct cfg_obj cfg_obj_t;
typedef struct MDB_env MDB_env;
struct dns_view {
/* Unlocked. */
unsigned int magic;
@ -203,28 +210,26 @@ struct dns_view {
ISC_LINK(struct dns_view) link;
dns_viewlist_t *viewlist;
dns_zone_t *managed_keys;
dns_zone_t *redirect;
dns_name_t *redirectzone; /* points to
* redirectfixed
* when valid */
dns_zone_t *managed_keys;
dns_zone_t *redirect;
dns_name_t *redirectzone; /* points to redirectfixed when valid */
dns_fixedname_t redirectfixed;
/*
* File and configuration data for zones added at runtime
* (only used in BIND9).
*
* XXX: This should be a pointer to an opaque type that
* named implements.
* File and configuration data for zones added at runtime.
*/
char *new_zone_dir;
char *new_zone_file;
char *new_zone_db;
void *new_zone_dbenv;
uint64_t new_zone_mapsize;
void *new_zone_config;
void (*cfg_destroy)(void **);
isc_mutex_t new_zone_lock;
struct {
bool allowed;
char *dir;
char *file;
char *db;
MDB_env *dbenv;
uint64_t mapsize;
cfg_obj_t *vconfig;
cfg_obj_t *nzconfig;
void (*cleanup)(dns_view_t *);
isc_mutex_t lock;
} newzone;
unsigned char secret[32]; /* Client secret */
unsigned int v6bias;
@ -1047,44 +1052,6 @@ dns_view_istrusted(dns_view_t *view, const dns_name_t *keyname,
* \li 'dnskey' is valid.
*/
isc_result_t
dns_view_setnewzones(dns_view_t *view, bool allow, void *cfgctx,
void (*cfg_destroy)(void **), uint64_t mapsize);
/*%<
* Set whether or not to allow zones to be created or deleted at runtime.
*
* If 'allow' is true, determines the filename into which new zone
* configuration will be written. Preserves the configuration context
* (a pointer to which is passed in 'cfgctx') for use when parsing new
* zone configuration. 'cfg_destroy' points to a callback routine to
* destroy the configuration context when the view is destroyed. (This
* roundabout method is used in order to avoid libdns having a dependency
* on libisccfg and libbind9.)
*
* If 'allow' is false, removes any existing references to
* configuration context and frees any memory.
*
* Requires:
* \li 'view' is valid.
*
* Returns:
* \li ISC_R_SUCCESS
* \li ISC_R_NOSPACE
*/
void
dns_view_setnewzonedir(dns_view_t *view, const char *dir);
const char *
dns_view_getnewzonedir(dns_view_t *view);
/*%<
* Set/get the path to the directory in which NZF or NZD files should
* be stored. If the path was previously set to a non-NULL value,
* the previous value is freed.
*
* Requires:
* \li 'view' is valid.
*/
void
dns_view_restorekeyring(dns_view_t *view);

View file

@ -160,7 +160,7 @@ dns_view_create(isc_mem_t *mctx, dns_dispatchmgr_t *dispatchmgr,
view->mctx, UNREACH_HOLD_TIME_INITIAL_SEC,
UNREACH_HOLD_TIME_MAX_SEC, UNREACH_BACKOFF_ELIGIBLE_SEC);
isc_mutex_init(&view->new_zone_lock);
isc_mutex_init(&view->newzone.lock);
dns_order_create(view->mctx, &view->order);
@ -345,20 +345,16 @@ destroy(dns_view_t *view) {
dns_dt_detach(&view->dtenv);
}
#endif /* HAVE_DNSTAP */
dns_view_setnewzones(view, false, NULL, NULL, 0ULL);
if (view->new_zone_file != NULL) {
isc_mem_free(view->mctx, view->new_zone_file);
}
if (view->new_zone_dir != NULL) {
isc_mem_free(view->mctx, view->new_zone_dir);
if (view->newzone.cleanup != NULL) {
view->newzone.cleanup(view);
}
#ifdef HAVE_LMDB
if (view->new_zone_dbenv != NULL) {
mdb_env_close((MDB_env *)view->new_zone_dbenv);
view->new_zone_dbenv = NULL;
if (view->newzone.dbenv != NULL) {
mdb_env_close((MDB_env *)view->newzone.dbenv);
view->newzone.dbenv = NULL;
}
if (view->new_zone_db != NULL) {
isc_mem_free(view->mctx, view->new_zone_db);
if (view->newzone.db != NULL) {
isc_mem_free(view->mctx, view->newzone.db);
}
#endif /* HAVE_LMDB */
dns_fwdtable_destroy(&view->fwdtable);
@ -369,7 +365,7 @@ destroy(dns_view_t *view) {
if (view->unreachcache != NULL) {
dns_unreachcache_destroy(&view->unreachcache);
}
isc_mutex_destroy(&view->new_zone_lock);
isc_mutex_destroy(&view->newzone.lock);
isc_mutex_destroy(&view->lock);
isc_refcount_destroy(&view->references);
isc_refcount_destroy(&view->weakrefs);
@ -1680,185 +1676,6 @@ finish:
return answer;
}
/*
* Create path to a directory and a filename constructed from viewname.
* This is a front-end to isc_file_sanitize(), allowing backward
* compatibility to older versions when a file couldn't be expected
* to be in the specified directory but might be in the current working
* directory instead.
*
* It first tests for the existence of a file <viewname>.<suffix> in
* 'directory'. If the file does not exist, it checks again in the
* current working directory. If it does not exist there either,
* return the path inside the directory.
*
* Returns ISC_R_SUCCESS if a path to an existing file is found or
* a new path is created; returns ISC_R_NOSPACE if the path won't
* fit in 'buflen'.
*/
static isc_result_t
nz_legacy(const char *directory, const char *viewname, const char *suffix,
char *buffer, size_t buflen) {
isc_result_t result;
char newbuf[PATH_MAX];
result = isc_file_sanitize(directory, viewname, suffix, buffer, buflen);
if (result != ISC_R_SUCCESS) {
return result;
} else if (directory == NULL || isc_file_exists(buffer)) {
return ISC_R_SUCCESS;
} else {
/* Save buffer */
strlcpy(newbuf, buffer, sizeof(newbuf));
}
/*
* It isn't in the specified directory; check CWD.
*/
result = isc_file_sanitize(NULL, viewname, suffix, buffer, buflen);
if (result != ISC_R_SUCCESS || isc_file_exists(buffer)) {
return result;
}
/*
* File does not exist in either 'directory' or CWD,
* so use the path in 'directory'.
*/
strlcpy(buffer, newbuf, buflen);
return ISC_R_SUCCESS;
}
isc_result_t
dns_view_setnewzones(dns_view_t *view, bool allow, void *cfgctx,
void (*cfg_destroy)(void **), uint64_t mapsize) {
isc_result_t result = ISC_R_SUCCESS;
char buffer[1024];
#ifdef HAVE_LMDB
MDB_env *env = NULL;
int status;
#endif /* ifdef HAVE_LMDB */
#ifndef HAVE_LMDB
UNUSED(mapsize);
#endif /* ifndef HAVE_LMDB */
REQUIRE(DNS_VIEW_VALID(view));
REQUIRE((cfgctx != NULL && cfg_destroy != NULL) || !allow);
if (view->new_zone_file != NULL) {
isc_mem_free(view->mctx, view->new_zone_file);
}
#ifdef HAVE_LMDB
if (view->new_zone_dbenv != NULL) {
mdb_env_close((MDB_env *)view->new_zone_dbenv);
view->new_zone_dbenv = NULL;
}
if (view->new_zone_db != NULL) {
isc_mem_free(view->mctx, view->new_zone_db);
}
#endif /* HAVE_LMDB */
if (view->new_zone_config != NULL) {
view->cfg_destroy(&view->new_zone_config);
view->cfg_destroy = NULL;
}
if (!allow) {
return ISC_R_SUCCESS;
}
CHECK(nz_legacy(view->new_zone_dir, view->name, "nzf", buffer,
sizeof(buffer)));
view->new_zone_file = isc_mem_strdup(view->mctx, buffer);
#ifdef HAVE_LMDB
CHECK(nz_legacy(view->new_zone_dir, view->name, "nzd", buffer,
sizeof(buffer)));
view->new_zone_db = isc_mem_strdup(view->mctx, buffer);
status = mdb_env_create(&env);
if (status != MDB_SUCCESS) {
isc_log_write(DNS_LOGCATEGORY_GENERAL, ISC_LOGMODULE_OTHER,
ISC_LOG_ERROR, "mdb_env_create failed: %s",
mdb_strerror(status));
CHECK(ISC_R_FAILURE);
}
if (mapsize != 0ULL) {
status = mdb_env_set_mapsize(env, mapsize);
if (status != MDB_SUCCESS) {
isc_log_write(DNS_LOGCATEGORY_GENERAL,
ISC_LOGMODULE_OTHER, ISC_LOG_ERROR,
"mdb_env_set_mapsize failed: %s",
mdb_strerror(status));
CHECK(ISC_R_FAILURE);
}
view->new_zone_mapsize = mapsize;
}
status = mdb_env_open(env, view->new_zone_db, DNS_LMDB_FLAGS, 0600);
if (status != MDB_SUCCESS) {
isc_log_write(DNS_LOGCATEGORY_GENERAL, ISC_LOGMODULE_OTHER,
ISC_LOG_ERROR, "mdb_env_open of '%s' failed: %s",
view->new_zone_db, mdb_strerror(status));
CHECK(ISC_R_FAILURE);
}
view->new_zone_dbenv = env;
env = NULL;
#endif /* HAVE_LMDB */
view->new_zone_config = cfgctx;
view->cfg_destroy = cfg_destroy;
cleanup:
if (result != ISC_R_SUCCESS) {
if (view->new_zone_file != NULL) {
isc_mem_free(view->mctx, view->new_zone_file);
}
#ifdef HAVE_LMDB
if (view->new_zone_db != NULL) {
isc_mem_free(view->mctx, view->new_zone_db);
}
if (env != NULL) {
mdb_env_close(env);
}
#endif /* HAVE_LMDB */
view->new_zone_config = NULL;
view->cfg_destroy = NULL;
}
return result;
}
void
dns_view_setnewzonedir(dns_view_t *view, const char *dir) {
REQUIRE(DNS_VIEW_VALID(view));
if (view->new_zone_dir != NULL) {
isc_mem_free(view->mctx, view->new_zone_dir);
}
if (dir == NULL) {
return;
}
view->new_zone_dir = isc_mem_strdup(view->mctx, dir);
}
const char *
dns_view_getnewzonedir(dns_view_t *view) {
REQUIRE(DNS_VIEW_VALID(view));
return view->new_zone_dir;
}
isc_result_t
dns_view_searchdlz(dns_view_t *view, const dns_name_t *name,
unsigned int minlabels, dns_clientinfomethods_t *methods,

View file

@ -58,33 +58,18 @@ cfg_aclconfctx_create(isc_mem_t *mctx, cfg_aclconfctx_t **aclctxp) {
return ISC_R_SUCCESS;
}
void
cfg_aclconfctx_attach(cfg_aclconfctx_t *src, cfg_aclconfctx_t **dest) {
REQUIRE(src != NULL);
REQUIRE(dest != NULL && *dest == NULL);
isc_refcount_increment(&src->references);
*dest = src;
}
void
cfg_aclconfctx_detach(cfg_aclconfctx_t **aclctxp) {
REQUIRE(aclctxp != NULL && *aclctxp != NULL);
cfg_aclconfctx_t *aclctx = *aclctxp;
*aclctxp = NULL;
if (isc_refcount_decrement(&aclctx->references) == 1) {
isc_refcount_destroy(&aclctx->references);
ISC_LIST_FOREACH(aclctx->named_acl_cache, dacl, nextincache) {
ISC_LIST_UNLINK(aclctx->named_acl_cache, dacl,
nextincache);
dns_acl_detach(&dacl);
}
isc_mem_putanddetach(&aclctx->mctx, aclctx, sizeof(*aclctx));
static void
destroy_aclctx(cfg_aclconfctx_t *aclctx) {
isc_refcount_destroy(&aclctx->references);
ISC_LIST_FOREACH(aclctx->named_acl_cache, dacl, nextincache) {
ISC_LIST_UNLINK(aclctx->named_acl_cache, dacl, nextincache);
dns_acl_detach(&dacl);
}
isc_mem_putanddetach(&aclctx->mctx, aclctx, sizeof(*aclctx));
}
ISC_REFCOUNT_IMPL(cfg_aclconfctx, destroy_aclctx);
/*
* Find the definition of the named acl whose name is "name".
*/

View file

@ -39,19 +39,6 @@ cfg_aclconfctx_create(isc_mem_t *mctx, cfg_aclconfctx_t **aclctxp);
* Creates and initializes an ACL configuration context.
*/
void
cfg_aclconfctx_detach(cfg_aclconfctx_t **aclctxp);
/*
* Removes a reference to an ACL configuration context; when references
* reaches zero, clears the contents and deallocate the structure.
*/
void
cfg_aclconfctx_attach(cfg_aclconfctx_t *src, cfg_aclconfctx_t **dest);
/*
* Attaches a pointer to an existing ACL configuration context.
*/
isc_result_t
cfg_acl_fromconfig(const cfg_obj_t *caml, const cfg_obj_t *cctx,
cfg_aclconfctx_t *ctx, isc_mem_t *mctx,
@ -72,3 +59,5 @@ cfg_acl_fromconfig(const cfg_obj_t *caml, const cfg_obj_t *cctx,
* 'ctx' to be non NULL.
* '*target' to be NULL or a valid dns_acl_t.
*/
ISC_REFCOUNT_DECL(cfg_aclconfctx);