Compare commits

...
Author SHA1 Message Date
Willem TooropandGitHub 2f2d9229e7 Merge branch 'master' into hackathon/get-root-zone-with-etag 2026-10-20 14:39:58 +02:00
Yorgos Thessalonikefs 3d429e7fed Changelog entry for #1536:
- Merge #1536: Better documentation and warning message for no
  configured trust anchors.
2026-10-20 14:16:13 +02:00
Yorgos Thessalonikefs ed5b3b23b3 Merge branch 'validator-warn-no-trust' 2026-10-20 14:13:49 +02:00
Yorgos Thessalonikefs 970e7db1a0 - Better wording. 2026-10-20 12:41:46 +02:00
Yorgos Thessalonikefs ed954b6c18 - Add CONTRIBUTING.md, similar to other NLnet Labs projects. 2026-10-20 12:04:49 +02:00
W.C.A. Wijngaards 024730aca7 - Merge #1534: unbound-checkconf reads config, but zonefiles
are read with an option, '-a'. This speeds up the checkconf
  before server start in the presence of large zone files.
  And it is similar to how nsd-checkconf behaves.
2026-10-20 09:11:09 +02:00
Wouter WijngaardsandGitHub 660bfddeba unbound-checkconf reads config, but zonefiles are read with an option (#1534)
* - checkconf-zone-check, unbound-checkonf only reads config, -a option to read
  zone files of auth-zones and RPZ.

* - checkconf-zone-check, documentation in the man page.
2026-10-20 09:09:29 +02:00
Yorgos Thessalonikefs 3ed72c7681 - Fix #1535: [FR] refuse validator configuration without any trust anchor.
Better documentation and warning message.
2026-10-19 17:44:19 +02:00
d4999d418c Apply suggestions from code review
Co-authored-by: Wouter Wijngaards <wcawijngaards@users.noreply.github.com>
2026-03-27 16:03:40 +01:00
Willem Toorop 7994365385 Send along ETag with auth zone xfrs over http
When fetching an authoritative zone over http(s), store the ETag if it
was send along in the response, and subsequentally send that along in
subsequent http(s) requests as a "If-None-Match: <ETag>| header.

This will prevent Unbound from fetching the full root zone every 30 minutes
when configured for local root wiht an url, but will instead only fetch
it when there is a different version.
2026-03-14 16:28:31 +08:00
16 changed files with 171 additions and 37 deletions
+54
View File
@@ -0,0 +1,54 @@
# Contributing to Unbound
This document offers guidelines on how to best contribute to Unbound, whether
it being new features or correcting flaws or bugs.
## Code of Conduct
This project and everyone participating in it is governed by the [NLnet Labs
Code of Conduct](https://www.nlnetlabs.nl/conduct/).
By participating, you are expected to uphold this code.
## LLM Policy
Before contributing to this project, please be aware of the [NLnet Labs LLM Policy](https://www.nlnetlabs.nl/llm-policy).
In a nutshell: we do not accept any LLM-generated contributions to
code or documentation.
## Join the Community
We invite you to join the [NLnet Labs Commununity
Forum](https://community.nlnetlabs.nl/) where Unbound has its own category.
Please don't open a GitHub issue for a question.
Instead, follow the discussion on the forum and ask questions there before you
start sending patches.
We prefer public discussions over private ones, so everyone in the community
can participate and learn.
## License and copyright
When contributing with code, you agree to put your changes and new code under
the same license Unbound is already using.
When changing existing source code, do not alter the copyright of the original
file(s).
The copyright will still be owned by the original creator(s) or those who have
been assigned copyright by the original author(s).
By submitting a patch to Unbound, you are assumed to have the right to the code
and to be allowed by your employer or whatever to hand over that patch/code
to us.
We will credit you for your changes as far as possible, to give credit but also
to keep a trace back to who made what changes.
## Sharing Your Changes
We would like you to submit a [pull request on
GitHub](https://github.com/NLnetLabs/Unbound/pulls).
Please note that you can create a draft pull request to indicate that you're
still working on something but still share it with the maintainers to get some
early feedback.
Once final, your change will be reviewed and discussed on GitHub.
You will be expected to correct flaws pointed out and update accordingly.
As a submitter of a change, you are the owner of that change until it has been
merged.
+1 -1
View File
@@ -1077,7 +1077,7 @@ daemon_fork(struct daemon* daemon)
daemon_setup_modules(daemon);
/* read auth zonefiles */
if(!auth_zones_apply_cfg(daemon->env->auth_zones, daemon->cfg, 1,
if(!auth_zones_apply_cfg(daemon->env->auth_zones, daemon->cfg, 1, 1,
&daemon->use_rpz, daemon->env, &daemon->mods))
fatal_exit("auth_zones could not be setup");
+1 -1
View File
@@ -5980,7 +5980,7 @@ fr_construct_from_config(struct fast_reload_thread* fr,
fr_construct_clear(ct);
return 0;
}
if(!auth_zones_apply_cfg(ct->auth_zones, newcfg, 1, &ct->use_rpz,
if(!auth_zones_apply_cfg(ct->auth_zones, newcfg, 1, 1, &ct->use_rpz,
fr->worker->daemon->env, &fr->worker->daemon->mods)) {
fr_construct_clear(ct);
return 0;
+11
View File
@@ -1,3 +1,14 @@
20 October 2026: Wouter
- Merge #1534: unbound-checkconf reads config, but zonefiles
are read with an option, '-a'. This speeds up the checkconf
before server start in the presence of large zone files.
And it is similar to how nsd-checkconf behaves.
20 October 2026: Yorgos
- Add CONTRIBUTING.md, similar to other NLnet Labs projects.
- Merge #1536: Better documentation and warning message for no
configured trust anchors.
19 October 2026: Wouter
- Merge #1528 from Ar-maan05: Fix NSEC at apex detection for
short type bitmaps.
+1
View File
@@ -672,6 +672,7 @@ server:
# most modules have to be listed at the beginning of the line,
# except cachedb(just before iterator), and python (at the beginning,
# or, just before the iterator).
# Note that validator needs configured trust-anchors to be of any use.
# module-config: "validator iterator"
# File with trusted keys, kept up-to-date using RFC5011 probes,
+6 -1
View File
@@ -42,7 +42,7 @@ unbound-checkconf(8)
Synopsis
--------
**unbound-checkconf** [``-hf``] [``-o option``] [cfgfile]
**unbound-checkconf** [``-ahf``] [``-o option``] [cfgfile]
Description
-----------
@@ -54,6 +54,11 @@ The config file syntax is described in
The available options are:
.. option:: -a
Also read the zonefiles for auth-zones and RPZ.
It checks if the syntax is okay of the zonefiles.
.. option:: -h
Show the version and commandline option help.
+4 -3
View File
@@ -2171,11 +2171,12 @@ These options are part of the ``server:`` section.
The ordering of the modules is significant, the order decides the order
of processing.
Setting this to just "iterator" will result in a non-validating server.
Setting this to "validator iterator" will turn on DNSSEC validation.
Setting this to just ``"iterator"`` results in a non-validating server.
Setting this to ``"validator iterator"`` (default) enables the DNSSEC
validator module but not DNSSEC validation.
.. note::
You must also set trust-anchors for validation to be useful.
To turn on DNSSEC validation you must configure trust-anchors.
Adding ``respip`` to the front will cause RPZ processing to be done on all
queries.
+1 -1
View File
@@ -86,7 +86,7 @@ context_finalize(struct ub_ctx* ctx)
return UB_NOMEM;
if(!local_zones_apply_cfg(ctx->local_zones, cfg))
return UB_INITFAIL;
if(!auth_zones_apply_cfg(ctx->env->auth_zones, cfg, 1, &is_rpz,
if(!auth_zones_apply_cfg(ctx->env->auth_zones, cfg, 1, 1, &is_rpz,
ctx->env, &ctx->mods))
return UB_INITFAIL;
if(!(ctx->env->fwds = forwards_create()) ||
+47 -14
View File
@@ -2350,7 +2350,7 @@ az_delete_deleted_zones(struct auth_zones* az)
}
int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg,
int setup, int* is_rpz, struct module_env* env,
int setup, int readauth, int* is_rpz, struct module_env* env,
struct module_stack* mods)
{
struct config_auth* p;
@@ -2367,8 +2367,10 @@ int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg,
}
}
az_delete_deleted_zones(az);
if(!auth_zones_read_zones(az, cfg, env, mods))
return 0;
if(readauth) {
if(!auth_zones_read_zones(az, cfg, env, mods))
return 0;
}
if(setup) {
if(!auth_zones_setup_zones(az))
return 0;
@@ -5236,7 +5238,7 @@ apply_axfr(struct auth_xfer* xfr, struct auth_zone* z,
/** apply HTTP to zone in memory. z is locked. false on failure(mallocfail) */
static int
apply_http(struct auth_xfer* xfr, struct auth_zone* z,
struct sldns_buffer* scratch_buffer)
struct sldns_buffer* scratch_buffer, char* etag)
{
/* parse data in chunks */
/* parse RR's and read into memory. ignore $INCLUDE from the
@@ -5332,7 +5334,11 @@ apply_http(struct auth_xfer* xfr, struct auth_zone* z,
pstate.lineno,
sldns_buffer_begin(scratch_buffer));
return 0;
}
}
if (etag) {
verbose(VERB_OPS, "etag %s stored", etag);
strcpy(z->etag, etag);
}
return 1;
}
@@ -5490,7 +5496,7 @@ static int xfr_process_reacquire_locks(struct auth_xfer* xfr,
* return false if it did not work */
static int
xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env,
int* ixfr_fail)
int* ixfr_fail, char *etag)
{
struct auth_zone* z;
int zonemd_in_progress;
@@ -5506,7 +5512,7 @@ xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env,
/* apply data */
if(xfr->task_transfer->master->http) {
if(!apply_http(xfr, z, env->scratch_buffer)) {
if(!apply_http(xfr, z, env->scratch_buffer, etag)) {
auth_zone_clear_data(z);
lock_rw_unlock(&z->lock);
verbose(VERB_ALGO, "http from %s: could not store data",
@@ -5786,6 +5792,21 @@ xfr_transfer_init_fetch(struct auth_xfer* xfr, struct module_env* env)
xfr->task_transfer->start_time = *env->now_tv;
if(master->http) {
char etag[128], *etag_str = NULL;
struct auth_zone* z;
/* Obtain locks and structures. */
lock_basic_unlock(&xfr->lock);
if(!xfr_process_reacquire_locks(xfr, env, &z)) {
/* The zone is gone, ignore xfr transfer. */
return 1; /* Stop processing this xfr. */
}
/* Holding xfr and z locks. */
if(z && z->etag[0] && strlen(z->etag) < sizeof(etag)) {
strlcpy(etag, z->etag, sizeof(etag));
etag_str = etag;
}
lock_rw_unlock(&z->lock);
/* perform http fetch */
/* store http port number into sockaddr,
* unless someone used unbound's host@port notation */
@@ -5795,7 +5816,7 @@ xfr_transfer_init_fetch(struct auth_xfer* xfr, struct module_env* env)
xfr->task_transfer->cp = outnet_comm_point_for_http(
env->outnet, auth_xfer_transfer_http_callback, xfr,
&addr, addrlen, -1, master->ssl, master->host,
master->file, env->cfg);
master->file, env->cfg, etag_str);
if(!xfr->task_transfer->cp) {
char zname[LDNS_MAX_DOMAINLEN], as[256];
dname_str(xfr->name, zname);
@@ -6387,10 +6408,10 @@ xfer_link_data(sldns_buffer* pkt, struct auth_xfer* xfr)
/** task transfer. the list of data is complete. process it and if failed
* move to next master, if succeeded, end the task transfer */
static void
process_list_end_transfer(struct auth_xfer* xfr, struct module_env* env)
process_list_end_transfer(struct auth_xfer* xfr, struct module_env* env, char* etag)
{
int ixfr_fail = 0;
if(xfr_process_chunk_list(xfr, env, &ixfr_fail)) {
if(xfr_process_chunk_list(xfr, env, &ixfr_fail, etag)) {
/* it worked! */
auth_chunks_delete(xfr->task_transfer);
@@ -6562,9 +6583,15 @@ auth_xfer_transfer_tcp_callback(struct comm_point* c, void* arg, int err,
}
/* if the transfer is done now, disconnect and process the list */
if(transferdone) {
comm_point_delete(xfr->task_transfer->cp);
/* Convey etag (captured on the comm_point) to auth zone,
* if there was any */
struct comm_point *cp;
cp = xfr->task_transfer->cp;
xfr->task_transfer->cp = NULL;
process_list_end_transfer(xfr, env);
process_list_end_transfer(xfr, env,
( xfr->task_transfer->master->http && cp->etag[0]
? cp->etag : NULL));
comm_point_delete(cp);
return 0;
}
@@ -6646,11 +6673,17 @@ auth_xfer_transfer_http_callback(struct comm_point* c, void* arg, int err,
}
/* if the transfer is done now, disconnect and process the list */
if(err == NETEVENT_DONE) {
/* Convey etag (captured on the comm_point) to auth zone,
* if there was any */
struct comm_point *cp;
if(repinfo) repinfo->c = NULL; /* signal cp deleted to
the routine calling this callback */
comm_point_delete(xfr->task_transfer->cp);
cp = xfr->task_transfer->cp;
xfr->task_transfer->cp = NULL;
process_list_end_transfer(xfr, env);
process_list_end_transfer(xfr, env,
( xfr->task_transfer->master->http && cp->etag[0]
? cp->etag : NULL));
comm_point_delete(cp);
return 0;
}
+5 -1
View File
@@ -125,6 +125,8 @@ struct auth_zone {
int zone_expired;
/** zone is a slave zone (it has masters) */
int zone_is_slave;
/** store etag */
char etag[128];
/** for downstream: this zone answers queries towards the downstream
* clients */
int for_downstream;
@@ -514,13 +516,15 @@ struct auth_zones* auth_zones_create(void);
* @param az: auth zones structure
* @param cfg: config to apply.
* @param setup: if true, also sets up values in the auth zones structure
* @param readauth: if true, reads zone files. False to omit zone file read,
* for use by checkconf.
* @param is_rpz: set to 1 if at least one RPZ zone is configured.
* @param env: environment for offline verification.
* @param mods: modules in environment.
* @return false on failure.
*/
int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg,
int setup, int* is_rpz, struct module_env* env,
int setup, int readauth, int* is_rpz, struct module_env* env,
struct module_stack* mods);
/** initial pick up of worker timeouts, ties events to worker event loop
+7 -3
View File
@@ -3900,11 +3900,15 @@ setup_http_user_agent(sldns_buffer* buf, struct config_file* cfg)
/** setup http request headers in buffer for sending query to destination */
static int
setup_http_request(sldns_buffer* buf, char* host, char* path,
struct config_file* cfg)
struct config_file* cfg, const char* etag)
{
sldns_buffer_clear(buf);
sldns_buffer_printf(buf, "GET /%s HTTP/1.1\r\n", path);
sldns_buffer_printf(buf, "Host: %s\r\n", host);
if (etag) {
verbose(VERB_OPS, "sending along ETag %s in http request", etag);
sldns_buffer_printf(buf, "If-None-Match: %s\r\n", etag);
}
setup_http_user_agent(buf, cfg);
/* We do not really do multiple queries per connection,
* but this header setting is also not needed.
@@ -3921,7 +3925,7 @@ struct comm_point*
outnet_comm_point_for_http(struct outside_network* outnet,
comm_point_callback_type* cb, void* cb_arg,
struct sockaddr_storage* to_addr, socklen_t to_addrlen, int timeout,
int ssl, char* host, char* path, struct config_file* cfg)
int ssl, char* host, char* path, struct config_file* cfg, char* etag)
{
/* cp calls cb with err=NETEVENT_DONE when transfer is done */
struct comm_point* cp;
@@ -3959,7 +3963,7 @@ outnet_comm_point_for_http(struct outside_network* outnet,
comm_point_start_listening(cp, fd, timeout);
/* setup http request in cp->buffer */
if(!setup_http_request(cp->buffer, host, path, cfg)) {
if(!setup_http_request(cp->buffer, host, path, cfg, etag)) {
log_err("error setting up http request");
comm_point_delete(cp);
return NULL;
+1 -1
View File
@@ -854,7 +854,7 @@ struct comm_point* outnet_comm_point_for_tcp(struct outside_network* outnet,
struct comm_point* outnet_comm_point_for_http(struct outside_network* outnet,
comm_point_callback_type* cb, void* cb_arg,
struct sockaddr_storage* to_addr, socklen_t to_addrlen, int timeout,
int ssl, char* host, char* path, struct config_file* cfg);
int ssl, char* host, char* path, struct config_file* cfg, char* etag);
/** connect tcp connection to addr, 0 on failure */
int outnet_tcp_connect(int s, struct sockaddr_storage* addr, socklen_t addrlen);
+13 -6
View File
@@ -90,6 +90,7 @@ usage(void)
printf("Usage: unbound-checkconf [file]\n");
printf(" Checks unbound configuration file for errors.\n");
printf("file if omitted %s is used.\n", CONFIGFILE);
printf("-a read zone files of auth-zones and RPZ.\n");
printf("-o option print value of option to stdout.\n");
printf("-f output full pathname with chroot applied, eg. with -o pidfile.\n");
printf("-q quiet (suppress output on success).\n");
@@ -1166,11 +1167,12 @@ check_hints(struct config_file* cfg)
/** check auth zones */
static void
check_auth(struct config_file* cfg)
check_auth(struct config_file* cfg, int readauth)
{
int is_rpz = 0;
struct auth_zones* az = auth_zones_create();
if(!az || !auth_zones_apply_cfg(az, cfg, 0, &is_rpz, NULL, NULL)) {
if(!az || !auth_zones_apply_cfg(az, cfg, 0, readauth, &is_rpz, NULL,
NULL)) {
fatal_exit("Could not setup authority zones");
}
if(is_rpz && !strstr(cfg->module_conf, "respip"))
@@ -1180,7 +1182,8 @@ check_auth(struct config_file* cfg)
/** check config file */
static void
checkconf(const char* cfgfile, const char* opt, int final, int quiet)
checkconf(const char* cfgfile, const char* opt, int final, int quiet,
int readauth)
{
char oldwd[4096];
struct config_file* cfg = config_create();
@@ -1212,7 +1215,7 @@ checkconf(const char* cfgfile, const char* opt, int final, int quiet)
#endif
check_fwd(cfg);
check_hints(cfg);
check_auth(cfg);
check_auth(cfg, readauth);
if(!quiet) { printf("unbound-checkconf: no errors in %s\n", cfgfile); }
config_delete(cfg);
}
@@ -1228,6 +1231,7 @@ int main(int argc, char* argv[])
int c;
int final = 0;
int quiet = 0;
int readauth = 0;
const char* f;
const char* opt = NULL;
const char* cfgfile = CONFIGFILE;
@@ -1240,8 +1244,11 @@ int main(int argc, char* argv[])
cfgfile = CONFIGFILE;
#endif /* USE_WINSOCK */
/* parse the options */
while( (c=getopt(argc, argv, "fhqo:")) != -1) {
while( (c=getopt(argc, argv, "afhqo:")) != -1) {
switch(c) {
case 'a':
readauth = 1;
break;
case 'f':
final = 1;
break;
@@ -1264,7 +1271,7 @@ int main(int argc, char* argv[])
if(argc == 1)
f = argv[0];
else f = cfgfile;
checkconf(f, opt, final, quiet);
checkconf(f, opt, final, quiet, readauth);
checklock_stop();
return 0;
}
+5
View File
@@ -5037,6 +5037,10 @@ http_process_initial_header(struct comm_point* c)
} else if(strncasecmp(line, "Transfer-Encoding: chunked", 19+7) == 0) {
c->tcp_byte_count = 0;
c->http_is_chunked = 1;
} else if(strncasecmp(line, "ETag: ", 6) == 0) {
if (strlen(line + 6) < sizeof(c->etag)) {
strcpy(c->etag, line + 6);
}
} else if(line[0] == 0) {
/* end of initial headers */
c->http_in_headers = 0;
@@ -6581,6 +6585,7 @@ comm_point_create_http_out(struct comm_base *base, size_t bufsize,
c->http_in_chunk_headers = 0;
c->http_is_chunked = 0;
c->http_temp = temp;
c->etag[0] = 0;
#ifdef USE_MSG_FASTOPEN
c->tcp_do_fastopen = 1;
#endif
+2
View File
@@ -286,6 +286,8 @@ struct comm_point {
struct sldns_buffer* http_temp;
/** http stored content in buffer */
size_t http_stored;
/** http Entity-Tag */
char etag[128];
/* -------- HTTP/2 ------- */
/** http2 session */
struct http2_session* h2_session;
+12 -5
View File
@@ -231,6 +231,7 @@ val_init(struct module_env* env, int id)
{
struct val_env* val_env = (struct val_env*)calloc(1,
sizeof(struct val_env));
struct trust_anchor* anchor = NULL;
if(!val_env) {
log_err("malloc failure");
return 0;
@@ -247,16 +248,22 @@ val_init(struct module_env* env, int id)
log_err("validator: could not apply configuration settings.");
return 0;
}
if(env->cfg->disable_edns_do) {
struct trust_anchor* anchor = anchors_find_any_noninsecure(
env->anchors);
if(anchor) {
anchor = anchors_find_any_noninsecure(env->anchors);
if(anchor) {
if(env->cfg->disable_edns_do) {
char b[LDNS_MAX_DOMAINLEN];
dname_str(anchor->name, b);
log_warn("validator: disable-edns-do is enabled, but there is a trust anchor for '%s'. Since DNSSEC could not work, the disable-edns-do setting is turned off. Continuing without it.", b);
lock_basic_unlock(&anchor->lock);
env->cfg->disable_edns_do = 0;
}
lock_basic_unlock(&anchor->lock);
} else {
log_warn("validator: no DNSSEC trust anchors configured. "
"DNSSEC validation is now effectively DISABLED. "
"Either configure a trust anchor to enable DNSSEC "
"validation or remove the validator from "
"module-config.");
}
return 1;