From a2ac98073785d06e0cd3fc8b50bdd6fe2921fa70 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 15 Nov 2024 10:37:35 +0100 Subject: [PATCH 001/218] - Fix #1183: the data being used is released in method nsec3_hash_test_entry. --- doc/Changelog | 4 ++++ testcode/unitverify.c | 1 + 2 files changed, 5 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index abb60af22..7b80f2c0e 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +15 November 2024: Wouter + - Fix #1183: the data being used is released in method + nsec3_hash_test_entry. + 8 November 2024: Yorgos - More descriptive text for 'harden-algo-downgrade'. - Complete fix for max-global-quota to 200. diff --git a/testcode/unitverify.c b/testcode/unitverify.c index 275435c73..806e4a6ff 100644 --- a/testcode/unitverify.c +++ b/testcode/unitverify.c @@ -458,6 +458,7 @@ nsec3_hash_test_entry(struct entry* e, rbtree_type* ct, reply_info_parsedelete(rep, alloc); query_info_clear(&qinfo); + rbtree_init(ct, &nsec3_hash_cmp); /* remove refs freed by parsedelete */ } From 4cf7fae50c633452a852134775b3b69bc03147d1 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 15 Nov 2024 10:47:27 +0100 Subject: [PATCH 002/218] - Fix for #1183: release nsec3 hashes per test file. --- doc/Changelog | 1 + testcode/unitverify.c | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 7b80f2c0e..4a9d8f902 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,7 @@ 15 November 2024: Wouter - Fix #1183: the data being used is released in method nsec3_hash_test_entry. + - Fix for #1183: release nsec3 hashes per test file. 8 November 2024: Yorgos - More descriptive text for 'harden-algo-downgrade'. diff --git a/testcode/unitverify.c b/testcode/unitverify.c index 806e4a6ff..81c8b13c6 100644 --- a/testcode/unitverify.c +++ b/testcode/unitverify.c @@ -425,7 +425,7 @@ nsec3_hash_test_entry(struct entry* e, rbtree_type* ct, { struct query_info qinfo; struct reply_info* rep = NULL; - struct ub_packed_rrset_key* answer, *nsec3; + struct ub_packed_rrset_key* answer, *nsec3, *nsec3_region; struct nsec3_cached_hash* hash = NULL; int ret; uint8_t* qname; @@ -443,7 +443,11 @@ nsec3_hash_test_entry(struct entry* e, rbtree_type* ct, /* check test is OK */ unit_assert(nsec3 && answer && qname); - ret = nsec3_hash_name(ct, region, buf, nsec3, 0, qname, + /* Copy the nsec3 to the region, so it can stay referenced by the + * ct tree entry. The region is freed when the file is done. */ + nsec3_region = packed_rrset_copy_region(nsec3, region, 0); + + ret = nsec3_hash_name(ct, region, buf, nsec3_region, 0, qname, qinfo.qname_len, &hash); if(ret < 1) { printf("Bad nsec3_hash_name retcode %d\n", ret); @@ -458,7 +462,6 @@ nsec3_hash_test_entry(struct entry* e, rbtree_type* ct, reply_info_parsedelete(rep, alloc); query_info_clear(&qinfo); - rbtree_init(ct, &nsec3_hash_cmp); /* remove refs freed by parsedelete */ } From 2c72a4970b3cb6eb261579c71ae67706dd620901 Mon Sep 17 00:00:00 2001 From: Sergey Kacheev Date: Tue, 29 Oct 2024 17:21:52 +0300 Subject: [PATCH 003/218] fix: lock-free counters for auth_zone up/down queries --- daemon/stats.c | 18 ++++-------------- iterator/iterator.c | 4 +--- services/authzone.c | 8 ++------ services/authzone.h | 4 ---- services/mesh.c | 2 ++ services/mesh.h | 5 +++++ 6 files changed, 14 insertions(+), 27 deletions(-) diff --git a/daemon/stats.c b/daemon/stats.c index 0e17300a1..1eb7d458f 100644 --- a/daemon/stats.c +++ b/daemon/stats.c @@ -325,20 +325,8 @@ server_stats_compile(struct worker* worker, struct ub_stats_info* s, int reset) s->svr.num_query_dnscrypt_replay = 0; #endif /* USE_DNSCRYPT */ if(worker->env.auth_zones) { - if(reset && !worker->env.cfg->stat_cumulative) { - lock_rw_wrlock(&worker->env.auth_zones->lock); - } else { - lock_rw_rdlock(&worker->env.auth_zones->lock); - } - s->svr.num_query_authzone_up = (long long)worker->env. - auth_zones->num_query_up; - s->svr.num_query_authzone_down = (long long)worker->env. - auth_zones->num_query_down; - if(reset && !worker->env.cfg->stat_cumulative) { - worker->env.auth_zones->num_query_up = 0; - worker->env.auth_zones->num_query_down = 0; - } - lock_rw_unlock(&worker->env.auth_zones->lock); + s->svr.num_query_authzone_up += (long long)worker->env.mesh->num_query_authzone_up; + s->svr.num_query_authzone_down += (long long)worker->env.mesh->num_query_authzone_down; } s->svr.mem_stream_wait = (long long)tcp_req_info_get_stream_buffer_size(); @@ -454,6 +442,8 @@ void server_stats_add(struct ub_stats_info* total, struct ub_stats_info* a) total->svr.num_queries_missed_cache += a->svr.num_queries_missed_cache; total->svr.num_queries_prefetch += a->svr.num_queries_prefetch; total->svr.num_queries_timed_out += a->svr.num_queries_timed_out; + total->svr.num_query_authzone_up += a->svr.num_query_authzone_up; + total->svr.num_query_authzone_down += a->svr.num_query_authzone_down; if (total->svr.max_query_time_us < a->svr.max_query_time_us) total->svr.max_query_time_us = a->svr.max_query_time_us; total->svr.sum_query_list_size += a->svr.sum_query_list_size; diff --git a/iterator/iterator.c b/iterator/iterator.c index 59e4b36ce..a2119d89b 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -2741,9 +2741,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, if((iq->chase_flags&BIT_RD) && !(iq->response->rep->flags&BIT_AA)) { verbose(VERB_ALGO, "forwarder, ignoring referral from auth zone"); } else { - lock_rw_wrlock(&qstate->env->auth_zones->lock); - qstate->env->auth_zones->num_query_up++; - lock_rw_unlock(&qstate->env->auth_zones->lock); + qstate->env->mesh->num_query_authzone_up++; iq->num_current_queries++; iq->chase_to_rd = 0; iq->dnssec_lame_query = 0; diff --git a/services/authzone.c b/services/authzone.c index 6f6c55d43..8019c491f 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -3610,9 +3610,7 @@ int auth_zones_answer(struct auth_zones* az, struct module_env* env, return 0; } lock_rw_unlock(&z->lock); - lock_rw_wrlock(&az->lock); - az->num_query_down++; - lock_rw_unlock(&az->lock); + env->mesh->num_query_authzone_down++; auth_error_encode(qinfo, env, edns, repinfo, buf, temp, LDNS_RCODE_SERVFAIL); return 1; @@ -3625,9 +3623,7 @@ int auth_zones_answer(struct auth_zones* az, struct module_env* env, /* fallback to regular answering (recursive) */ return 0; } - lock_rw_wrlock(&az->lock); - az->num_query_down++; - lock_rw_unlock(&az->lock); + env->mesh->num_query_authzone_down++; /* encode answer */ if(!r) diff --git a/services/authzone.h b/services/authzone.h index 07614ed82..3994a4ead 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -78,10 +78,6 @@ struct auth_zones { rbtree_type xtree; /** do we have downstream enabled */ int have_downstream; - /** number of queries upstream */ - size_t num_query_up; - /** number of queries downstream */ - size_t num_query_down; /** first auth zone containing rpz item in linked list */ struct auth_zone* rpz_first; /** rw lock for rpz linked list, needed when iterating or editing linked diff --git a/services/mesh.c b/services/mesh.c index d512ab3d3..cf9280590 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -2029,6 +2029,8 @@ mesh_stats_clear(struct mesh_area* mesh) { if(!mesh) return; + mesh->num_query_authzone_up = 0; + mesh->num_query_authzone_down = 0; mesh->replies_sent = 0; mesh->replies_sum_wait.tv_sec = 0; mesh->replies_sum_wait.tv_usec = 0; diff --git a/services/mesh.h b/services/mesh.h index 26ececbe6..9dcf6ab31 100644 --- a/services/mesh.h +++ b/services/mesh.h @@ -90,6 +90,11 @@ struct mesh_area { /** rbtree of all current queries (mesh_state.node)*/ rbtree_type all; + /** number of queries for unbound's auth_zones, upstream query */ + size_t num_query_authzone_up; + /** number of queries for unbound's auth_zones, downstream answers */ + size_t num_query_authzone_down; + /** count of the total number of mesh_reply entries */ size_t num_reply_addrs; /** count of the number of mesh_states that have mesh_replies From 9a3a1bc221e8398ecdce117150e3a4c324ebe8e8 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 19 Nov 2024 17:01:34 +0100 Subject: [PATCH 004/218] Changelog entry for #1169: - Merge #1169 from Sergey Kacheev, fix: lock-free counters for auth_zone up/down queries. --- doc/Changelog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 4a9d8f902..c6f8d3c22 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +19 November 2024: Yorgos + - Merge #1169 from Sergey Kacheev, fix: lock-free counters for + auth_zone up/down queries. + 15 November 2024: Wouter - Fix #1183: the data being used is released in method nsec3_hash_test_entry. From e75da7d954b940c3f38ac21434247ee2d889feee Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 20 Nov 2024 10:53:45 +0100 Subject: [PATCH 005/218] - Fix comparison to help static analyzer. --- doc/Changelog | 3 +++ util/netevent.c | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index c6f8d3c22..53074a9a9 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +20 November 2024: Yorgos + - Fix comparison to help static analyzer. + 19 November 2024: Yorgos - Merge #1169 from Sergey Kacheev, fix: lock-free counters for auth_zone up/down queries. diff --git a/util/netevent.c b/util/netevent.c index b36f00f1a..9181d3e4a 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -2845,6 +2845,7 @@ static int doq_lookup_conn_stream(struct comm_reply* repinfo, struct comm_point* c, struct doq_conn** conn, struct doq_stream** stream) { + log_assert(c->doq_socket); if(c->doq_socket->current_conn) { *conn = c->doq_socket->current_conn; } else { @@ -6747,7 +6748,7 @@ comm_point_drop_reply(struct comm_reply* repinfo) reclaim_http_handler(repinfo->c); return; #ifdef HAVE_NGTCP2 - } else if(repinfo->c->type == comm_doq) { + } else if(repinfo->c->doq_socket) { doq_socket_drop_reply(repinfo); return; #endif From eefdbb341f19fe17d91bd5ded73357c1f33e395b Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 22 Nov 2024 15:30:51 +0100 Subject: [PATCH 006/218] - Fix #1175: serve-expired does not adhere to secure-by-default principle. The default value of serve-expired-client-timeout is set to 1800 as suggested by RFC8767. --- doc/Changelog | 5 +++++ doc/example.conf.in | 10 +++++----- doc/unbound.conf.5.in | 19 +++++++++++-------- util/config_file.c | 2 +- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 53074a9a9..65533799b 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,8 @@ +22 November 2024: Yorgos + - Fix #1175: serve-expired does not adhere to secure-by-default + principle. The default value of serve-expired-client-timeout + is set to 1800 as suggested by RFC8767. + 20 November 2024: Yorgos - Fix comparison to help static analyzer. diff --git a/doc/example.conf.in b/doc/example.conf.in index 55fea6a42..e0ee39ad4 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -731,7 +731,8 @@ server: # disable-edns-do: no # Serve expired responses from cache, with serve-expired-reply-ttl in - # the response, and then attempt to fetch the data afresh. + # the response. By default it first tries to refresh an expired answer. + # Can be configured with serve-expired-client-timeout. # serve-expired: no # # Limit serving of expired responses to configured seconds after @@ -749,10 +750,9 @@ server: # # Time in milliseconds before replying to the client with expired data. # This essentially enables the serve-stale behavior as specified in - # RFC 8767 that first tries to resolve before - # immediately responding with expired data. 0 disables this behavior. - # A recommended value is 1800. - # serve-expired-client-timeout: 0 + # RFC 8767 that first tries to resolve before immediately responding + # with expired data. 0 disables this behavior. + # serve-expired-client-timeout: 1800 # Return the original TTL as received from the upstream name server rather # than the decrementing TTL as stored in the cache. Enabling this feature diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 159afc673..cf6f14915 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -1402,9 +1402,10 @@ Default is no. .TP .B serve\-expired: \fI If enabled, Unbound attempts to serve old responses from cache with a -TTL of \fBserve\-expired\-reply\-ttl\fR in the response without waiting for the -actual resolution to finish. The actual resolution answer ends up in the cache -later on. Default is "no". +TTL of \fBserve\-expired\-reply\-ttl\fR in the response. +By default the expired answer will be used after a resolution attempt errored +out or is taking more than serve\-expired\-client\-timeout to resolve. +Default is "no". .TP .B serve\-expired\-ttl: \fI Limit serving of expired responses to configured seconds after expiration. 0 @@ -1424,12 +1425,14 @@ TTL value to use when replying with expired data. If use 30 as the value (RFC 8767). The default is 30. .TP .B serve\-expired\-client\-timeout: \fI -Time in milliseconds before replying to the client with expired data. This -essentially enables the serve-stale behavior as specified in +Time in milliseconds before replying to the client with expired data. +This essentially enables the serve-stale behavior as specified in RFC 8767 that first tries to resolve before immediately -responding with expired data. A recommended value per -RFC 8767 is 1800. Setting this to 0 will disable this -behavior. Default is 0. +responding with expired data. +Setting this to 0 will disable this behavior and instead serve the expired +record immediately from the cache before attempting to refresh it via +resolution. +Default is 1800. .TP .B serve\-original\-ttl: \fI If enabled, Unbound will always return the original TTL as received from diff --git a/util/config_file.c b/util/config_file.c index 247d7c9f4..c1c55c529 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -283,7 +283,7 @@ config_create(void) cfg->serve_expired_ttl = 0; cfg->serve_expired_ttl_reset = 0; cfg->serve_expired_reply_ttl = 30; - cfg->serve_expired_client_timeout = 0; + cfg->serve_expired_client_timeout = 1800; cfg->ede_serve_expired = 0; cfg->serve_original_ttl = 0; cfg->zonemd_permissive_mode = 0; From 9e3c50ec9e0f6a84d2d62e9ff9f2a7c4308a752f Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 22 Nov 2024 16:14:02 +0100 Subject: [PATCH 007/218] - For #1175, update serve-expired tests. --- doc/Changelog | 1 + testdata/cachedb_expired.crpl | 1 + testdata/cachedb_expired_reply_ttl.crpl | 1 + testdata/cachedb_servfail_cname.crpl | 2 +- testdata/cachedb_val_expired.crpl | 1 + testdata/dnstap.tdir/dnstap.conf | 5 +++-- testdata/fwd_0ttlservfail.rpl | 1 + testdata/serve_expired.rpl | 1 + testdata/serve_expired_0ttl_nodata.rpl | 1 + testdata/serve_expired_0ttl_nxdomain.rpl | 1 + testdata/serve_expired_0ttl_servfail.rpl | 1 + testdata/serve_expired_cached_servfail.rpl | 1 + testdata/serve_expired_cached_servfail_refresh.rpl | 1 + testdata/serve_expired_reply_ttl.rpl | 1 + testdata/serve_expired_ttl.rpl | 1 + testdata/serve_expired_ttl_reset.rpl | 1 + testdata/serve_expired_val_bogus.rpl | 1 + testdata/serve_expired_zerottl.rpl | 1 + testdata/serve_original_ttl.rpl | 1 + testdata/stat_values.tdir/stat_values.conf | 1 + testdata/subnet_cached_servfail.crpl | 1 + testdata/subnet_global_prefetch_always_forward.crpl | 1 + testdata/subnet_global_prefetch_expired.crpl | 1 + 23 files changed, 25 insertions(+), 3 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 65533799b..0403e0c10 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,7 @@ - Fix #1175: serve-expired does not adhere to secure-by-default principle. The default value of serve-expired-client-timeout is set to 1800 as suggested by RFC8767. + - For #1175, update serve-expired tests. 20 November 2024: Yorgos - Fix comparison to help static analyzer. diff --git a/testdata/cachedb_expired.crpl b/testdata/cachedb_expired.crpl index 9f9ff677c..d3bf06fe1 100644 --- a/testdata/cachedb_expired.crpl +++ b/testdata/cachedb_expired.crpl @@ -4,6 +4,7 @@ server: qname-minimisation: no minimal-responses: no serve-expired: yes + serve-expired-client-timeout: 0 module-config: "cachedb iterator" cachedb: diff --git a/testdata/cachedb_expired_reply_ttl.crpl b/testdata/cachedb_expired_reply_ttl.crpl index b5f340505..03fd01add 100644 --- a/testdata/cachedb_expired_reply_ttl.crpl +++ b/testdata/cachedb_expired_reply_ttl.crpl @@ -4,6 +4,7 @@ server: qname-minimisation: no minimal-responses: no serve-expired: yes + serve-expired-client-timeout: 0 serve-expired-reply-ttl: 30 module-config: "cachedb iterator" diff --git a/testdata/cachedb_servfail_cname.crpl b/testdata/cachedb_servfail_cname.crpl index 221f00d4d..99b3d51f9 100644 --- a/testdata/cachedb_servfail_cname.crpl +++ b/testdata/cachedb_servfail_cname.crpl @@ -3,7 +3,7 @@ server: target-fetch-policy: "0 0 0 0 0" qname-minimisation: no minimal-responses: no - ;serve-expired: yes + serve-expired: no module-config: "cachedb iterator" cachedb: diff --git a/testdata/cachedb_val_expired.crpl b/testdata/cachedb_val_expired.crpl index 4a51e8272..741445ce8 100644 --- a/testdata/cachedb_val_expired.crpl +++ b/testdata/cachedb_val_expired.crpl @@ -4,6 +4,7 @@ server: qname-minimisation: no minimal-responses: yes serve-expired: yes + serve-expired-client-timeout: 0 ;module-config: "subnetcache validator cachedb iterator" module-config: "validator cachedb iterator" diff --git a/testdata/dnstap.tdir/dnstap.conf b/testdata/dnstap.tdir/dnstap.conf index fc382ccfd..b5497bfeb 100644 --- a/testdata/dnstap.tdir/dnstap.conf +++ b/testdata/dnstap.tdir/dnstap.conf @@ -12,8 +12,9 @@ server: do-not-query-localhost: no local-zone: "example.net." redirect local-data: "example.net. IN A 10.20.30.41" - serve-expired: yes - serve-expired-reply-ttl: 30 + serve-expired: yes + serve-expired-client-timeout: 0 + serve-expired-reply-ttl: 30 remote-control: control-enable: yes control-interface: 127.0.0.1 diff --git a/testdata/fwd_0ttlservfail.rpl b/testdata/fwd_0ttlservfail.rpl index ed912c73b..d50d386d4 100644 --- a/testdata/fwd_0ttlservfail.rpl +++ b/testdata/fwd_0ttlservfail.rpl @@ -2,6 +2,7 @@ ; config options go here. server: serve-expired: yes + serve-expired-client-timeout: 0 prefetch: yes forward-zone: name: "." forward-addr: 216.0.0.1 CONFIG_END diff --git a/testdata/serve_expired.rpl b/testdata/serve_expired.rpl index 3f61019fa..990a562c7 100644 --- a/testdata/serve_expired.rpl +++ b/testdata/serve_expired.rpl @@ -4,6 +4,7 @@ server: qname-minimisation: "no" minimal-responses: no serve-expired: yes + serve-expired-client-timeout: 0 access-control: 127.0.0.1/32 allow_snoop ede: yes ede-serve-expired: yes diff --git a/testdata/serve_expired_0ttl_nodata.rpl b/testdata/serve_expired_0ttl_nodata.rpl index 7f1b5a565..032706f23 100644 --- a/testdata/serve_expired_0ttl_nodata.rpl +++ b/testdata/serve_expired_0ttl_nodata.rpl @@ -4,6 +4,7 @@ server: qname-minimisation: "no" minimal-responses: no serve-expired: yes + serve-expired-client-timeout: 0 log-servfail: yes ede: yes ede-serve-expired: yes diff --git a/testdata/serve_expired_0ttl_nxdomain.rpl b/testdata/serve_expired_0ttl_nxdomain.rpl index 4adb4b839..146752658 100644 --- a/testdata/serve_expired_0ttl_nxdomain.rpl +++ b/testdata/serve_expired_0ttl_nxdomain.rpl @@ -4,6 +4,7 @@ server: qname-minimisation: "no" minimal-responses: no serve-expired: yes + serve-expired-client-timeout: 0 log-servfail: yes ede: yes ede-serve-expired: yes diff --git a/testdata/serve_expired_0ttl_servfail.rpl b/testdata/serve_expired_0ttl_servfail.rpl index 6833af17b..a3bf826fe 100644 --- a/testdata/serve_expired_0ttl_servfail.rpl +++ b/testdata/serve_expired_0ttl_servfail.rpl @@ -4,6 +4,7 @@ server: qname-minimisation: "no" minimal-responses: no serve-expired: yes + serve-expired-client-timeout: 0 log-servfail: yes ede: yes ede-serve-expired: yes diff --git a/testdata/serve_expired_cached_servfail.rpl b/testdata/serve_expired_cached_servfail.rpl index edec74479..3fd7dd274 100644 --- a/testdata/serve_expired_cached_servfail.rpl +++ b/testdata/serve_expired_cached_servfail.rpl @@ -4,6 +4,7 @@ server: qname-minimisation: "no" minimal-responses: no serve-expired: yes + serve-expired-client-timeout: 0 serve-expired-reply-ttl: 123 log-servfail: yes ede: yes diff --git a/testdata/serve_expired_cached_servfail_refresh.rpl b/testdata/serve_expired_cached_servfail_refresh.rpl index 4d14dd948..6ad73efa4 100644 --- a/testdata/serve_expired_cached_servfail_refresh.rpl +++ b/testdata/serve_expired_cached_servfail_refresh.rpl @@ -4,6 +4,7 @@ server: qname-minimisation: "no" minimal-responses: no serve-expired: yes + serve-expired-client-timeout: 0 serve-expired-reply-ttl: 123 log-servfail: yes ede: yes diff --git a/testdata/serve_expired_reply_ttl.rpl b/testdata/serve_expired_reply_ttl.rpl index 124fb874d..e76976bde 100644 --- a/testdata/serve_expired_reply_ttl.rpl +++ b/testdata/serve_expired_reply_ttl.rpl @@ -5,6 +5,7 @@ server: minimal-responses: no serve-expired: yes serve-expired-reply-ttl: 123 + serve-expired-client-timeout: 0 ede: yes ede-serve-expired: yes diff --git a/testdata/serve_expired_ttl.rpl b/testdata/serve_expired_ttl.rpl index df4ecb89d..66acbdcf1 100644 --- a/testdata/serve_expired_ttl.rpl +++ b/testdata/serve_expired_ttl.rpl @@ -4,6 +4,7 @@ server: qname-minimisation: "no" minimal-responses: no serve-expired: yes + serve-expired-client-timeout: 0 serve-expired-ttl: 10 stub-zone: diff --git a/testdata/serve_expired_ttl_reset.rpl b/testdata/serve_expired_ttl_reset.rpl index 521d5a0f0..faedb1cfc 100644 --- a/testdata/serve_expired_ttl_reset.rpl +++ b/testdata/serve_expired_ttl_reset.rpl @@ -4,6 +4,7 @@ server: serve-expired-ttl: 1 serve-expired-ttl-reset: yes serve-expired-reply-ttl: 123 + serve-expired-client-timeout: 0 ede: yes ede-serve-expired: yes forward-zone: name: "." forward-addr: 216.0.0.1 diff --git a/testdata/serve_expired_val_bogus.rpl b/testdata/serve_expired_val_bogus.rpl index 49afcc0eb..6c28aa9a6 100644 --- a/testdata/serve_expired_val_bogus.rpl +++ b/testdata/serve_expired_val_bogus.rpl @@ -10,6 +10,7 @@ server: minimal-responses: no serve-expired: yes + serve-expired-client-timeout: 0 serve-expired-reply-ttl: 123 ede: yes ede-serve-expired: yes diff --git a/testdata/serve_expired_zerottl.rpl b/testdata/serve_expired_zerottl.rpl index 0239b4a19..1411cb8e7 100644 --- a/testdata/serve_expired_zerottl.rpl +++ b/testdata/serve_expired_zerottl.rpl @@ -4,6 +4,7 @@ server: qname-minimisation: "no" minimal-responses: no serve-expired: yes + serve-expired-client-timeout: 0 serve-expired-reply-ttl: 123 ede: yes ede-serve-expired: yes diff --git a/testdata/serve_original_ttl.rpl b/testdata/serve_original_ttl.rpl index 24d01b6fe..30503c285 100644 --- a/testdata/serve_original_ttl.rpl +++ b/testdata/serve_original_ttl.rpl @@ -8,6 +8,7 @@ server: cache-max-ttl: 1000 cache-min-ttl: 20 serve-expired: yes + serve-expired-client-timeout: 0 serve-expired-reply-ttl: 123 ede: yes ede-serve-expired: yes diff --git a/testdata/stat_values.tdir/stat_values.conf b/testdata/stat_values.tdir/stat_values.conf index 7886f19bc..d1adff58c 100644 --- a/testdata/stat_values.tdir/stat_values.conf +++ b/testdata/stat_values.tdir/stat_values.conf @@ -14,6 +14,7 @@ server: outbound-msg-retry: 0 root-key-sentinel: no trust-anchor-signaling: no + serve-expired-client-timeout: 0 local-zone: local.zone static local-data: "www.local.zone A 192.0.2.1" diff --git a/testdata/subnet_cached_servfail.crpl b/testdata/subnet_cached_servfail.crpl index 9c746d579..f1a66159c 100644 --- a/testdata/subnet_cached_servfail.crpl +++ b/testdata/subnet_cached_servfail.crpl @@ -12,6 +12,7 @@ server: qname-minimisation: no minimal-responses: no serve-expired: yes + serve-expired-client-timeout: 0 prefetch: yes stub-zone: diff --git a/testdata/subnet_global_prefetch_always_forward.crpl b/testdata/subnet_global_prefetch_always_forward.crpl index ccfe5dfd6..775474cbc 100644 --- a/testdata/subnet_global_prefetch_always_forward.crpl +++ b/testdata/subnet_global_prefetch_always_forward.crpl @@ -6,6 +6,7 @@ server: trust-anchor-signaling: no target-fetch-policy: "0 0 0 0 0" serve-expired: yes + serve-expired-client-timeout: 0 client-subnet-always-forward: yes module-config: "subnetcache iterator" verbosity: 3 diff --git a/testdata/subnet_global_prefetch_expired.crpl b/testdata/subnet_global_prefetch_expired.crpl index de1b78055..374bf3e69 100644 --- a/testdata/subnet_global_prefetch_expired.crpl +++ b/testdata/subnet_global_prefetch_expired.crpl @@ -14,6 +14,7 @@ server: qname-minimisation: no minimal-responses: no serve-expired: yes + serve-expired-client-timeout: 0 serve-expired-ttl: 1 prefetch: yes From 06fb30d0a0d5ef36b1c5e6d18a63643dff3a1155 Mon Sep 17 00:00:00 2001 From: wenxuan70 Date: Sun, 24 Nov 2024 17:53:23 +0800 Subject: [PATCH 008/218] Fix the dname_str method to cause conversion errors when the domain name length is 255 --- util/data/dname.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/data/dname.c b/util/data/dname.c index 76b2ec7d6..cb78f5d63 100644 --- a/util/data/dname.c +++ b/util/data/dname.c @@ -654,7 +654,7 @@ void dname_str(uint8_t* dname, char* str) return; } len += lablen+1; - if(len >= LDNS_MAX_DOMAINLEN-1) { + if(len >= LDNS_MAX_DOMAINLEN) { *s++ = '&'; *s = 0; return; From 1cd2fb3b9d643ff66966d894877e99584a49b1e7 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 2 Dec 2024 10:03:35 +0100 Subject: [PATCH 009/218] - For #1189, add unit tests for dname_str() and debug check the input buffer size. --- testcode/unitdname.c | 147 +++++++++++++++++++++++++++++++++++++++++++ util/data/dname.c | 14 +++-- 2 files changed, 156 insertions(+), 5 deletions(-) diff --git a/testcode/unitdname.c b/testcode/unitdname.c index 6769127b9..a3fa0c508 100644 --- a/testcode/unitdname.c +++ b/testcode/unitdname.c @@ -39,6 +39,7 @@ */ #include "config.h" +#include #include "util/log.h" #include "testcode/unitmain.h" #include "util/data/dname.h" @@ -858,6 +859,151 @@ dname_setup_bufs(sldns_buffer* loopbuf, sldns_buffer* boundbuf) sldns_buffer_flip(boundbuf); } +static void +dname_test_str(sldns_buffer* buff) +{ + char result[LDNS_MAX_DOMAINLEN], expect[LDNS_MAX_DOMAINLEN], *e; + size_t i; + unit_show_func("util/data/dname.c", "dname_str"); + + /* root ; expected OK */ + sldns_buffer_clear(buff); + sldns_buffer_write(buff, "\000", 1); + sldns_buffer_flip(buff); + unit_assert( sldns_buffer_limit(buff) == 1 ); + unit_assert( pkt_dname_len(buff) == 1 ); + dname_str(sldns_buffer_begin(buff), result); + unit_assert( strcmp( ".", result) == 0 ); + + /* LDNS_MAX_DOMAINLEN - 1 ; expected OK */ + sldns_buffer_clear(buff); + sldns_buffer_write(buff, + "\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890" /* 64 up to here */ + "\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890" /* 128 up to here */ + "\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890" /* 192 up to here */ + "\074abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567" /* 253 up to here */ + "\000" /* 254 up to here */ + , 254); + sldns_buffer_flip(buff); + unit_assert( sldns_buffer_limit(buff) == 254 ); + unit_assert( pkt_dname_len(buff) == 254 ); + dname_str(sldns_buffer_begin(buff), result); + unit_assert( strcmp( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890." + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890." + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890." + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567" + ".", result) == 0 ); + + /* LDNS_MAX_DOMAINLEN ; expected OK */ + sldns_buffer_clear(buff); + sldns_buffer_write(buff, + "\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890" /* 64 up to here */ + "\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890" /* 128 up to here */ + "\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890" /* 192 up to here */ + "\075abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678" /* 254 up to here */ + "\000" /* 255 up to here */ + , 255); + sldns_buffer_flip(buff); + unit_assert( sldns_buffer_limit(buff) == 255 ); + unit_assert( pkt_dname_len(buff) == 255 ); + dname_str(sldns_buffer_begin(buff), result); + unit_assert( strcmp( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890." + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890." + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890." + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678" + ".", result) == 0 ); + + /* LDNS_MAX_DOMAINLEN + 1 ; expected to fail, output uses '&' on the latest label */ + sldns_buffer_clear(buff); + sldns_buffer_write(buff, + "\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890" /* 64 up to here */ + "\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890" /* 128 up to here */ + "\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890" /* 192 up to here */ + "\076abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" /* 255 up to here */ + "\000" /* 256 up to here */ + , 256); + sldns_buffer_flip(buff); + unit_assert( sldns_buffer_limit(buff) == 256 ); + unit_assert( pkt_dname_len(buff) == 0 ); + dname_str(sldns_buffer_begin(buff), result); + unit_assert( strcmp( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890." + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890." + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890." + "&", result) == 0 ); + + /* LDNS_MAX_LABELLEN + 1 ; expected to fail, output uses '#' on the offending label */ + sldns_buffer_clear(buff); + sldns_buffer_write(buff, + "\077abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890" /* 64 up to here */ + "\100abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890a" /* 129 up to here */ + "\000" /* 130 up to here */ + , 130); + sldns_buffer_flip(buff); + unit_assert( sldns_buffer_limit(buff) == 130 ); + unit_assert( pkt_dname_len(buff) == 0 ); + dname_str(sldns_buffer_begin(buff), result); + unit_assert( strcmp( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890." + "#", result) == 0 ); + + /* LDNS_MAX_DOMAINLEN with single labels; expected OK */ + sldns_buffer_clear(buff); + for(i=0; i<=252; i+=2) + sldns_buffer_write(buff, "\001a", 2); + sldns_buffer_write_u8(buff, 0); + sldns_buffer_flip(buff); + unit_assert( sldns_buffer_limit(buff) == 255 ); + unit_assert( pkt_dname_len(buff) == 255 ); + dname_str(sldns_buffer_begin(buff), result); + e = expect; + for(i=0; i<=252; i+=2) { + *e++ = 'a'; + *e++ = '.'; + } + *e = '\0'; + unit_assert( strcmp(expect, result) == 0 ); + + /* LDNS_MAX_DOMAINLEN + 1 with single labels; expected to fail, output uses '&' on the latest label */ + sldns_buffer_clear(buff); + for(i=0; i<=250; i+=2) + sldns_buffer_write(buff, "\001a", 2); + sldns_buffer_write(buff, "\002ab", 3); + sldns_buffer_write_u8(buff, 0); + sldns_buffer_flip(buff); + unit_assert( sldns_buffer_limit(buff) == 256 ); + unit_assert( pkt_dname_len(buff) == 0 ); + dname_str(sldns_buffer_begin(buff), result); + e = expect; + for(i=0; i<=250; i+=2) { + *e++ = 'a'; + *e++ = '.'; + } + *e++ = '&'; + *e = '\0'; + unit_assert( strcmp(expect, result) == 0 ); + + /* Only alphas, numericals and '-', '_' and '*' are allowed in the output */ + for(i=1; i<=255; i++) { + if(isalnum(i) || i == '-' || i == '_' || i == '*') + continue; + sldns_buffer_clear(buff); + sldns_buffer_write_u8(buff, 1); + sldns_buffer_write_u8(buff, (uint8_t)i); + sldns_buffer_write_u8(buff, 0); + sldns_buffer_flip(buff); + unit_assert( sldns_buffer_limit(buff) == 3 ); + unit_assert( pkt_dname_len(buff) == 3); + dname_str(sldns_buffer_begin(buff), result); + if(strcmp( "?.", result) != 0 ) { + log_err("ASCII value '0x%lX' allowed in string output", i); + unit_assert(0); + } + } +} + void dname_test(void) { sldns_buffer* loopbuf = sldns_buffer_new(14); @@ -884,6 +1030,7 @@ void dname_test(void) dname_test_topdomain(); dname_test_valid(); dname_test_has_label(); + dname_test_str(buff); sldns_buffer_free(buff); sldns_buffer_free(loopbuf); sldns_buffer_free(boundbuf); diff --git a/util/data/dname.c b/util/data/dname.c index cb78f5d63..f08760e2f 100644 --- a/util/data/dname.c +++ b/util/data/dname.c @@ -644,24 +644,24 @@ void dname_str(uint8_t* dname, char* str) if(!dname || !*dname) { *s++ = '.'; *s = 0; - return; + goto out; } lablen = *dname++; while(lablen) { if(lablen > LDNS_MAX_LABELLEN) { *s++ = '#'; *s = 0; - return; + goto out; } len += lablen+1; if(len >= LDNS_MAX_DOMAINLEN) { *s++ = '&'; *s = 0; - return; + goto out; } while(lablen--) { - if(isalnum((unsigned char)*dname) - || *dname == '-' || *dname == '_' + if(isalnum((unsigned char)*dname) + || *dname == '-' || *dname == '_' || *dname == '*') *s++ = *(char*)dname++; else { @@ -673,6 +673,10 @@ void dname_str(uint8_t* dname, char* str) lablen = *dname++; } *s = 0; + +out: + log_assert(s - str < LDNS_MAX_DOMAINLEN); + return; } int From f46acec35f4199a19a8e5400f6cd08586fede8f5 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 2 Dec 2024 11:53:56 +0100 Subject: [PATCH 010/218] - For #1189, homogenize the input buffer size for dname_str(). --- daemon/cachedump.c | 2 +- daemon/remote.c | 16 ++++---- daemon/worker.c | 4 +- iterator/iter_delegpt.c | 2 +- iterator/iter_fwd.c | 2 +- iterator/iter_hints.c | 2 +- iterator/iter_utils.c | 8 ++-- iterator/iterator.c | 12 +++--- libunbound/libworker.c | 2 +- pythonmod/interface.i | 2 +- respip/respip.c | 2 +- services/authzone.c | 84 +++++++++++++++++++------------------- services/cache/infra.c | 3 +- services/localzone.c | 8 ++-- services/outside_network.c | 6 +-- services/rpz.c | 15 +++---- testcode/fake_event.c | 2 +- testcode/unitneg.c | 2 +- testcode/unitzonemd.c | 2 +- util/data/dname.h | 5 ++- util/data/msgreply.c | 8 ++-- util/module.c | 8 ++-- util/net_help.c | 6 +-- validator/autotrust.c | 8 ++-- validator/val_anchor.c | 2 +- validator/validator.c | 2 +- 26 files changed, 110 insertions(+), 105 deletions(-) diff --git a/daemon/cachedump.c b/daemon/cachedump.c index a04b24e56..ba986c763 100644 --- a/daemon/cachedump.c +++ b/daemon/cachedump.c @@ -836,7 +836,7 @@ int print_deleg_lookup(RES* ssl, struct worker* worker, uint8_t* nm, struct delegpt* dp; struct dns_msg* msg; struct regional* region = worker->scratchpad; - char b[260]; + char b[LDNS_MAX_DOMAINLEN]; struct query_info qinfo; struct iter_hints_stub* stub; int nolock = 0; diff --git a/daemon/remote.c b/daemon/remote.c index 4d7cff4db..abba0f405 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -2141,7 +2141,7 @@ static int ssl_print_name_dp(RES* ssl, const char* str, uint8_t* nm, uint16_t dclass, struct delegpt* dp) { - char buf[257]; + char buf[LDNS_MAX_DOMAINLEN]; struct delegpt_ns* ns; struct delegpt_addr* a; int f = 0; @@ -2509,7 +2509,7 @@ do_insecure_remove(RES* ssl, struct worker* worker, char* arg) static void do_insecure_list(RES* ssl, struct worker* worker) { - char buf[257]; + char buf[LDNS_MAX_DOMAINLEN]; struct trust_anchor* a; if(worker->env.anchors) { RBTREE_FOR(a, struct trust_anchor*, worker->env.anchors->tree) { @@ -2606,7 +2606,7 @@ get_mesh_status(struct mesh_area* mesh, struct mesh_state* m, } } else if(s == module_wait_subquery) { /* look in subs from mesh state to see what */ - char nm[257]; + char nm[LDNS_MAX_DOMAINLEN]; struct mesh_state_ref* sub; snprintf(buf, len, "%s wants", modname); l = strlen(buf); @@ -2636,7 +2636,7 @@ do_dump_requestlist(RES* ssl, struct worker* worker) struct mesh_area* mesh; struct mesh_state* m; int num = 0; - char buf[257]; + char buf[LDNS_MAX_DOMAINLEN]; char timebuf[32]; char statbuf[10240]; if(!ssl_printf(ssl, "thread #%d\n", worker->thread_num)) @@ -2686,7 +2686,7 @@ dump_infra_host(struct lruhash_entry* e, void* arg) struct infra_key* k = (struct infra_key*)e->key; struct infra_data* d = (struct infra_data*)e->data; char ip_str[1024]; - char name[257]; + char name[LDNS_MAX_DOMAINLEN]; int port; if(a->ssl_failed) return; @@ -2953,7 +2953,7 @@ static void do_list_auth_zones(RES* ssl, struct auth_zones* az) { struct auth_zone* z; - char buf[257], buf2[256]; + char buf[LDNS_MAX_DOMAINLEN], buf2[256]; lock_rw_rdlock(&az->lock); RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_rdlock(&z->lock); @@ -2983,7 +2983,7 @@ static void do_list_local_zones(RES* ssl, struct local_zones* zones) { struct local_zone* z; - char buf[257]; + char buf[LDNS_MAX_DOMAINLEN]; lock_rw_rdlock(&zones->lock); RBTREE_FOR(z, struct local_zone*, &zones->ztree) { lock_rw_rdlock(&z->lock); @@ -3094,7 +3094,7 @@ rate_list(struct lruhash_entry* e, void* arg) struct ratelimit_list_arg* a = (struct ratelimit_list_arg*)arg; struct rate_key* k = (struct rate_key*)e->key; struct rate_data* d = (struct rate_data*)e->data; - char buf[257]; + char buf[LDNS_MAX_DOMAINLEN]; int lim = infra_find_ratelimit(a->infra, k->name, k->namelen); int max = infra_rate_max(d, a->now, a->backoff); if(a->all == 0) { diff --git a/daemon/worker.c b/daemon/worker.c index 713de3163..f44cd761a 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -1082,7 +1082,7 @@ answer_notify(struct worker* w, struct query_info* qinfo, if(verbosity >= VERB_DETAIL) { char buf[380]; - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; char sr[25]; dname_str(qinfo->qname, zname); sr[0]=0; @@ -1413,7 +1413,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error, return 0; } if(c->dnscrypt && !repinfo->is_dnscrypted) { - char buf[LDNS_MAX_DOMAINLEN+1]; + char buf[LDNS_MAX_DOMAINLEN]; /* Check if this is unencrypted and asking for certs */ worker_check_request(c->buffer, worker, &check_result); if(check_result.value != 0) { diff --git a/iterator/iter_delegpt.c b/iterator/iter_delegpt.c index c8b9a3ffe..be5bf482d 100644 --- a/iterator/iter_delegpt.c +++ b/iterator/iter_delegpt.c @@ -278,7 +278,7 @@ delegpt_count_addr(struct delegpt* dp, size_t* numaddr, size_t* numres, void delegpt_log(enum verbosity_value v, struct delegpt* dp) { - char buf[LDNS_MAX_DOMAINLEN+1]; + char buf[LDNS_MAX_DOMAINLEN]; struct delegpt_ns* ns; struct delegpt_addr* a; size_t missing=0, numns=0, numaddr=0, numres=0, numavail=0; diff --git a/iterator/iter_fwd.c b/iterator/iter_fwd.c index b9d42553a..709911656 100644 --- a/iterator/iter_fwd.c +++ b/iterator/iter_fwd.c @@ -129,7 +129,7 @@ forwards_insert_data(struct iter_forwards* fwd, uint16_t c, uint8_t* nm, node->namelabs = nmlabs; node->dp = dp; if(!rbtree_insert(fwd->tree, &node->node)) { - char buf[257]; + char buf[LDNS_MAX_DOMAINLEN]; dname_str(nm, buf); log_err("duplicate forward zone %s ignored.", buf); delegpt_free_mlc(dp); diff --git a/iterator/iter_hints.c b/iterator/iter_hints.c index 8b168271c..fb9d10413 100644 --- a/iterator/iter_hints.c +++ b/iterator/iter_hints.c @@ -181,7 +181,7 @@ hints_insert(struct iter_hints* hints, uint16_t c, struct delegpt* dp, node->noprime = (uint8_t)noprime; if(!name_tree_insert(&hints->tree, &node->node, dp->name, dp->namelen, dp->namelabs, c)) { - char buf[257]; + char buf[LDNS_MAX_DOMAINLEN]; dname_str(dp->name, buf); log_err("second hints for zone %s ignored.", buf); delegpt_free_mlc(dp); diff --git a/iterator/iter_utils.c b/iterator/iter_utils.c index 5198e9ef2..e5cf13eca 100644 --- a/iterator/iter_utils.c +++ b/iterator/iter_utils.c @@ -1492,8 +1492,8 @@ iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf, int stub_no_cache = stub->dp->no_cache; lock_rw_unlock(&qstate->env->fwds->lock); if(stub_no_cache) { - char qname[255+1]; - char dpname[255+1]; + char qname[LDNS_MAX_DOMAINLEN]; + char dpname[LDNS_MAX_DOMAINLEN]; dname_str(qinf->qname, qname); dname_str(stub->dp->name, dpname); verbose(VERB_ALGO, "stub for %s %s has no_cache", qname, dpname); @@ -1520,8 +1520,8 @@ iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf, int dp_no_cache = dp->no_cache; lock_rw_unlock(&qstate->env->hints->lock); if(dp_no_cache) { - char qname[255+1]; - char dpname[255+1]; + char qname[LDNS_MAX_DOMAINLEN]; + char dpname[LDNS_MAX_DOMAINLEN]; dname_str(qinf->qname, qname); dname_str(dp->name, dpname); verbose(VERB_ALGO, "forward for %s %s has no_cache", qname, dpname); diff --git a/iterator/iterator.c b/iterator/iterator.c index fdba52ddb..ec70a267b 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -1092,7 +1092,7 @@ auth_zone_delegpt(struct module_qstate* qstate, struct iter_qstate* iq, /* cache is blacklisted and fallback, and we * already have an auth_zone dp */ if(verbosity>=VERB_ALGO) { - char buf[255+1]; + char buf[LDNS_MAX_DOMAINLEN]; dname_str(z->name, buf); verbose(VERB_ALGO, "auth_zone %s " "fallback because cache blacklisted", @@ -1109,7 +1109,7 @@ auth_zone_delegpt(struct module_qstate* qstate, struct iter_qstate* iq, * validation failure, and the zone allows * fallback to the internet, query there. */ if(verbosity>=VERB_ALGO) { - char buf[255+1]; + char buf[LDNS_MAX_DOMAINLEN]; dname_str(z->name, buf); verbose(VERB_ALGO, "auth_zone %s " "fallback because cache blacklisted", @@ -2033,7 +2033,7 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, return 1; if(iq->depth > 0 && iq->target_count && iq->target_count[TARGET_COUNT_QUERIES] > MAX_TARGET_COUNT) { - char s[LDNS_MAX_DOMAINLEN+1]; + char s[LDNS_MAX_DOMAINLEN]; dname_str(qstate->qinfo.qname, s); verbose(VERB_QUERY, "request %s has exceeded the maximum " "number of glue fetches %d", s, @@ -2041,7 +2041,7 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, return 2; } if(iq->dp_target_count > MAX_DP_TARGET_COUNT) { - char s[LDNS_MAX_DOMAINLEN+1]; + char s[LDNS_MAX_DOMAINLEN]; dname_str(qstate->qinfo.qname, s); verbose(VERB_QUERY, "request %s has exceeded the maximum " "number of glue fetches %d to a single delegation point", @@ -2252,7 +2252,7 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, } if(iq->depth > 0 && iq->target_count && iq->target_count[TARGET_COUNT_QUERIES] > MAX_TARGET_COUNT) { - char s[LDNS_MAX_DOMAINLEN+1]; + char s[LDNS_MAX_DOMAINLEN]; dname_str(qstate->qinfo.qname, s); verbose(VERB_QUERY, "request %s has exceeded the maximum " "number of glue fetches %d", s, @@ -3044,7 +3044,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, target_count_increase_global_quota(iq, 1); if(iq->target_count && iq->target_count[TARGET_COUNT_GLOBAL_QUOTA] > MAX_GLOBAL_QUOTA) { - char s[LDNS_MAX_DOMAINLEN+1]; + char s[LDNS_MAX_DOMAINLEN]; dname_str(qstate->qinfo.qname, s); verbose(VERB_QUERY, "request %s has exceeded the maximum " "global quota on number of upstream queries %d", s, diff --git a/libunbound/libworker.c b/libunbound/libworker.c index da7d4c224..6b2bf7a3c 100644 --- a/libunbound/libworker.c +++ b/libunbound/libworker.c @@ -423,7 +423,7 @@ int libworker_bg(struct ub_ctx* ctx) static int fill_canon(struct ub_result* res, uint8_t* s) { - char buf[255+2]; + char buf[LDNS_MAX_DOMAINLEN]; dname_str(s, buf); res->canonname = strdup(buf); return res->canonname != 0; diff --git a/pythonmod/interface.i b/pythonmod/interface.i index 810b1449d..2040fb9e8 100644 --- a/pythonmod/interface.i +++ b/pythonmod/interface.i @@ -204,7 +204,7 @@ struct query_info { %inline %{ PyObject* dnameAsStr(PyObject* dname) { - char buf[LDNS_MAX_DOMAINLEN+1]; + char buf[LDNS_MAX_DOMAINLEN]; buf[0] = '\0'; dname_str((uint8_t*)PyBytes_AsString(dname), buf); return PyString_FromString(buf); diff --git a/respip/respip.c b/respip/respip.c index db48f176e..c0ecba160 100644 --- a/respip/respip.c +++ b/respip/respip.c @@ -961,7 +961,7 @@ respip_rewrite_reply(const struct query_info* qinfo, struct sockaddr_storage ss; socklen_t ss_len = 0; char nm[256], ip[256]; - char qn[255+1]; + char qn[LDNS_MAX_DOMAINLEN]; if(!rdata2sockaddr(rep->rrsets[rrset_id]->entry.data, ntohs(rep->rrsets[rrset_id]->rk.type), rr_id, &ss, &ss_len)) snprintf(ip, sizeof(ip), "invalidRRdata"); else diff --git a/services/authzone.c b/services/authzone.c index 8019c491f..90fd0235e 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -1578,7 +1578,7 @@ auth_zone_read_zonefile(struct auth_zone* z, struct config_file* cfg) cfg->chrootdir, strlen(cfg->chrootdir)) == 0) zfilename += strlen(cfg->chrootdir); if(verbosity >= VERB_ALGO) { - char nm[255+1]; + char nm[LDNS_MAX_DOMAINLEN]; dname_str(z->name, nm); verbose(VERB_ALGO, "read zonefile %s for %s", zfilename, nm); } @@ -1942,7 +1942,7 @@ static int auth_zone_zonemd_check_hash(struct auth_zone* z, unsupported_reason = *reason; /* continue to check for valid ZONEMD */ if(verbosity >= VERB_ALGO) { - char zstr[255+1]; + char zstr[LDNS_MAX_DOMAINLEN]; dname_str(z->name, zstr); verbose(VERB_ALGO, "auth-zone %s ZONEMD %d %d is unsupported: %s", zstr, (int)scheme, (int)hashalgo, *reason); } @@ -1950,7 +1950,7 @@ static int auth_zone_zonemd_check_hash(struct auth_zone* z, continue; } if(verbosity >= VERB_ALGO) { - char zstr[255+1]; + char zstr[LDNS_MAX_DOMAINLEN]; dname_str(z->name, zstr); if(!*reason) verbose(VERB_ALGO, "auth-zone %s ZONEMD hash is correct", zstr); @@ -1973,7 +1973,7 @@ static int auth_zone_zonemd_check_hash(struct auth_zone* z, if(!*reason) *reason = "no ZONEMD records found"; if(verbosity >= VERB_ALGO) { - char zstr[255+1]; + char zstr[LDNS_MAX_DOMAINLEN]; dname_str(z->name, zstr); verbose(VERB_ALGO, "auth-zone %s ZONEMD failed: %s", zstr, *reason); } @@ -4799,8 +4799,8 @@ log_rrlist_position(const char* label, struct auth_chunk* rr_chunk, { sldns_buffer pkt; size_t dlen; - uint8_t buf[256]; - char str[256]; + uint8_t buf[LDNS_MAX_DOMAINLEN]; + char str[LDNS_MAX_DOMAINLEN]; char typestr[32]; sldns_buffer_init_frm_data(&pkt, rr_chunk->data, rr_chunk->len); sldns_buffer_set_position(&pkt, (size_t)(rr_dname - @@ -5227,7 +5227,7 @@ xfr_write_after_update(struct auth_xfer* xfr, struct module_env* env) cfg->chrootdir, strlen(cfg->chrootdir)) == 0) zfilename += strlen(cfg->chrootdir); if(verbosity >= VERB_ALGO) { - char nm[255+1]; + char nm[LDNS_MAX_DOMAINLEN]; dname_str(z->name, nm); verbose(VERB_ALGO, "write zonefile %s for %s", zfilename, nm); } @@ -5344,7 +5344,7 @@ xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env, /* holding z lock */ auth_zone_verify_zonemd(z, env, &env->mesh->mods, NULL, 0, 0); if(z->zone_expired) { - char zname[256]; + char zname[LDNS_MAX_DOMAINLEN]; /* ZONEMD must have failed */ /* reacquire locks, so we hold xfr lock on exit of routine, * and both xfr and z again after releasing xfr for potential @@ -5376,7 +5376,7 @@ xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env, lock_rw_unlock(&z->lock); if(verbosity >= VERB_QUERY && xfr->have_zone) { - char zname[256]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_QUERY, "auth zone %s updated to serial %u", zname, (unsigned)xfr->serial); @@ -5438,7 +5438,7 @@ xfr_transfer_lookup_host(struct auth_xfer* xfr, struct module_env* env) qinfo.local_alias = NULL; if(verbosity >= VERB_ALGO) { char buf1[512]; - char buf2[LDNS_MAX_DOMAINLEN+1]; + char buf2[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, buf2); snprintf(buf1, sizeof(buf1), "auth zone %s: master lookup" " for task_transfer", buf2); @@ -5494,7 +5494,7 @@ xfr_transfer_init_fetch(struct auth_xfer* xfr, struct module_env* env) /* the ones that are not in addr format are supposed * to be looked up. The lookup has failed however, * so skip them */ - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); log_err("%s: failed lookup, cannot transfer from master %s", zname, master->host); @@ -5533,7 +5533,7 @@ xfr_transfer_init_fetch(struct auth_xfer* xfr, struct module_env* env) &addr, addrlen, -1, master->ssl, master->host, master->file, env->cfg); if(!xfr->task_transfer->cp) { - char zname[255+1], as[256]; + char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "cannot create http cp " @@ -5542,7 +5542,7 @@ xfr_transfer_init_fetch(struct auth_xfer* xfr, struct module_env* env) } comm_timer_set(xfr->task_transfer->timer, &t); if(verbosity >= VERB_ALGO) { - char zname[255+1], as[256]; + char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "auth zone %s transfer next HTTP fetch from %s started", zname, as); @@ -5565,7 +5565,7 @@ xfr_transfer_init_fetch(struct auth_xfer* xfr, struct module_env* env) env->scratch_buffer, -1, auth_name != NULL, auth_name); if(!xfr->task_transfer->cp) { - char zname[255+1], as[256]; + char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "cannot create tcp cp connection for " @@ -5574,7 +5574,7 @@ xfr_transfer_init_fetch(struct auth_xfer* xfr, struct module_env* env) } comm_timer_set(xfr->task_transfer->timer, &t); if(verbosity >= VERB_ALGO) { - char zname[255+1], as[256]; + char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "auth zone %s transfer next %s fetch from %s started", zname, @@ -5598,7 +5598,7 @@ xfr_transfer_nexttarget_or_end(struct auth_xfer* xfr, struct module_env* env) * and that calls the callback just like a full * lookup and lookup failures also call callback */ if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s transfer next target lookup", zname); } @@ -5621,7 +5621,7 @@ xfr_transfer_nexttarget_or_end(struct auth_xfer* xfr, struct module_env* env) xfr_transfer_nextmaster(xfr); } if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s transfer failed, wait", zname); } @@ -5724,14 +5724,14 @@ void auth_xfer_transfer_lookup_callback(void* arg, int rcode, sldns_buffer* buf, lookup_target, answer, wanted_qtype); } else { if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s transfer lookup has nodata", zname, xfr->task_transfer->lookup_target->host, (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); } } } else { if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s transfer lookup has no answer", zname, xfr->task_transfer->lookup_target->host, (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); } @@ -5739,7 +5739,7 @@ void auth_xfer_transfer_lookup_callback(void* arg, int rcode, sldns_buffer* buf, regional_free_all(temp); } else { if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s transfer lookup failed", zname, xfr->task_transfer->lookup_target->host, (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); } @@ -6381,7 +6381,7 @@ xfr_probe_send_probe(struct auth_xfer* xfr, struct module_env* env, /* the ones that are not in addr format are supposed * to be looked up. The lookup has failed however, * so skip them */ - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); log_err("%s: failed lookup, cannot probe to master %s", zname, master->host); @@ -6423,7 +6423,7 @@ xfr_probe_send_probe(struct auth_xfer* xfr, struct module_env* env, xfr->task_probe->cp = outnet_comm_point_for_udp(env->outnet, auth_xfer_probe_udp_callback, xfr, &addr, addrlen); if(!xfr->task_probe->cp) { - char zname[255+1], as[256]; + char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "cannot create udp cp for " @@ -6443,7 +6443,7 @@ xfr_probe_send_probe(struct auth_xfer* xfr, struct module_env* env, /* send udp packet */ if(!comm_point_send_udp_msg(xfr->task_probe->cp, env->scratch_buffer, (struct sockaddr*)&addr, addrlen, 0)) { - char zname[255+1], as[256]; + char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "failed to send soa probe for %s to %s", @@ -6451,7 +6451,7 @@ xfr_probe_send_probe(struct auth_xfer* xfr, struct module_env* env, return 0; } if(verbosity >= VERB_ALGO) { - char zname[255+1], as[256]; + char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "auth zone %s soa probe sent to %s", zname, @@ -6482,7 +6482,7 @@ auth_xfer_probe_timer_callback(void* arg) } if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s soa probe timeout", zname); } @@ -6530,7 +6530,7 @@ auth_xfer_probe_udp_callback(struct comm_point* c, void* arg, int err, &serial)) { /* successful lookup */ if(verbosity >= VERB_ALGO) { - char buf[256]; + char buf[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, buf); verbose(VERB_ALGO, "auth zone %s: soa probe " "serial is %u", buf, (unsigned)serial); @@ -6569,14 +6569,14 @@ auth_xfer_probe_udp_callback(struct comm_point* c, void* arg, int err, } } else { if(verbosity >= VERB_ALGO) { - char buf[256]; + char buf[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, buf); verbose(VERB_ALGO, "auth zone %s: bad reply to soa probe", buf); } } } else { if(verbosity >= VERB_ALGO) { - char buf[256]; + char buf[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, buf); verbose(VERB_ALGO, "auth zone %s: soa probe failed", buf); } @@ -6633,7 +6633,7 @@ xfr_probe_lookup_host(struct auth_xfer* xfr, struct module_env* env) qinfo.local_alias = NULL; if(verbosity >= VERB_ALGO) { char buf1[512]; - char buf2[LDNS_MAX_DOMAINLEN+1]; + char buf2[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, buf2); snprintf(buf1, sizeof(buf1), "auth zone %s: master lookup" " for task_probe", buf2); @@ -6679,7 +6679,7 @@ xfr_probe_send_or_end(struct auth_xfer* xfr, struct module_env* env) * and that calls the callback just like a full * lookup and lookup failures also call callback */ if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s probe next target lookup", zname); } @@ -6692,7 +6692,7 @@ xfr_probe_send_or_end(struct auth_xfer* xfr, struct module_env* env) * allow_notify addrs */ probe_copy_masters_for_allow_notify(xfr); if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s probe: notify addrs updated", zname); } @@ -6700,7 +6700,7 @@ xfr_probe_send_or_end(struct auth_xfer* xfr, struct module_env* env) /* only wanted lookups for copy, stop probe and start wait */ xfr->task_probe->only_lookup = 0; if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s probe: finished only_lookup", zname); } @@ -6726,7 +6726,7 @@ xfr_probe_send_or_end(struct auth_xfer* xfr, struct module_env* env) if(xfr->task_probe->have_new_lease) { /* if zone not updated, start the wait timer again */ if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth_zone %s unchanged, new lease, wait", zname); } @@ -6737,7 +6737,7 @@ xfr_probe_send_or_end(struct auth_xfer* xfr, struct module_env* env) xfr_set_timeout(xfr, env, 0, 0); } else { if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s soa probe failed, wait to retry", zname); } @@ -6787,14 +6787,14 @@ void auth_xfer_probe_lookup_callback(void* arg, int rcode, sldns_buffer* buf, lookup_target, answer, wanted_qtype); } else { if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s probe lookup has nodata", zname, xfr->task_probe->lookup_target->host, (xfr->task_probe->lookup_aaaa?"AAAA":"A")); } } } else { if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s probe lookup has no address", zname, xfr->task_probe->lookup_target->host, (xfr->task_probe->lookup_aaaa?"AAAA":"A")); } @@ -6802,7 +6802,7 @@ void auth_xfer_probe_lookup_callback(void* arg, int rcode, sldns_buffer* buf, regional_free_all(temp); } else { if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s probe lookup failed", zname, xfr->task_probe->lookup_target->host, (xfr->task_probe->lookup_aaaa?"AAAA":"A")); } @@ -6976,7 +6976,7 @@ xfr_set_timeout(struct auth_xfer* xfr, struct module_env* env, if(!xfr->task_nextprobe->timer) { /* failed to malloc memory. likely zone transfer * also fails for that. skip the timeout */ - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); log_err("cannot allocate timer, no refresh for %s", zname); @@ -6997,7 +6997,7 @@ xfr_set_timeout(struct auth_xfer* xfr, struct module_env* env, xfr->task_probe->only_lookup = 1; } if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s timeout in %d seconds", zname, (int)tv.tv_sec); @@ -7784,7 +7784,7 @@ static void auth_zone_log(uint8_t* name, enum verbosity_value level, va_list args; va_start(args, format); if(verbosity >= level) { - char str[255+1]; + char str[LDNS_MAX_DOMAINLEN]; char msg[MAXSYSLOGMSGLEN]; dname_str(name, str); vsnprintf(msg, sizeof(msg), format, args); @@ -7986,7 +7986,7 @@ static int zonemd_check_dnssec_soazonemd(struct auth_zone* z, static void auth_zone_zonemd_fail(struct auth_zone* z, struct module_env* env, char* reason, char* why_bogus, char** result) { - char zstr[255+1]; + char zstr[LDNS_MAX_DOMAINLEN]; /* if fail: log reason, and depending on config also take action * and drop the zone, eg. it is gone from memory, set zone_expired */ dname_str(z->name, zstr); @@ -8432,7 +8432,7 @@ zonemd_lookup_dnskey(struct auth_zone* z, struct module_env* env) qinfo.local_alias = NULL; if(verbosity >= VERB_ALGO) { char buf1[512]; - char buf2[LDNS_MAX_DOMAINLEN+1]; + char buf2[LDNS_MAX_DOMAINLEN]; dname_str(z->name, buf2); snprintf(buf1, sizeof(buf1), "auth zone %s: lookup %s " "for zonemd verification", buf2, diff --git a/services/cache/infra.c b/services/cache/infra.c index 66b17c121..bb3ee87e9 100644 --- a/services/cache/infra.c +++ b/services/cache/infra.c @@ -1081,7 +1081,8 @@ int infra_ratelimit_inc(struct infra_cache* infra, uint8_t* name, lock_rw_unlock(&entry->lock); if(premax <= lim && max > lim) { - char buf[257], qnm[257], ts[12], cs[12], ip[128]; + char buf[LDNS_MAX_DOMAINLEN], qnm[LDNS_MAX_DOMAINLEN]; + char ts[12], cs[12], ip[128]; dname_str(name, buf); dname_str(qinfo->qname, qnm); sldns_wire2str_type_buf(qinfo->qtype, ts, sizeof(ts)); diff --git a/services/localzone.c b/services/localzone.c index d21e0c48a..aa7138d6a 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -223,7 +223,7 @@ lz_enter_zone_dname(struct local_zones* zones, uint8_t* nm, size_t len, lock_rw_wrlock(&z->lock); if(!rbtree_insert(&zones->ztree, &z->node)) { struct local_zone* oldz; - char str[256]; + char str[LDNS_MAX_DOMAINLEN]; dname_str(nm, str); log_warn("duplicate local-zone %s", str); lock_rw_unlock(&z->lock); @@ -1765,7 +1765,7 @@ lz_inform_print(struct local_zone* z, struct query_info* qinfo, struct sockaddr_storage* addr, socklen_t addrlen) { char ip[128], txt[512]; - char zname[LDNS_MAX_DOMAINLEN+1]; + char zname[LDNS_MAX_DOMAINLEN]; uint16_t port = ntohs(((struct sockaddr_in*)addr)->sin_port); dname_str(z->name, zname); addr_to_str(addr, addrlen, ip, sizeof(ip)); @@ -1875,7 +1875,7 @@ local_zones_answer(struct local_zones* zones, struct module_env* env, return 0; } if(z && verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(z->name, zname); verbose(VERB_ALGO, "using localzone %s %s from view %s", zname, local_zone_type2str(lzt), view->name); @@ -1897,7 +1897,7 @@ local_zones_answer(struct local_zones* zones, struct module_env* env, z->override_tree, &tag, tagname, num_tags); lock_rw_unlock(&zones->lock); if(z && verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(z->name, zname); verbose(VERB_ALGO, "using localzone %s %s", zname, local_zone_type2str(lzt)); diff --git a/services/outside_network.c b/services/outside_network.c index 58f1e6d58..b9475a368 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -1054,7 +1054,7 @@ reuse_move_writewait_away(struct outside_network* outnet, if(verbosity >= VERB_CLIENT && pend->query->pkt_len > 12+2+2 && LDNS_QDCOUNT(pend->query->pkt) > 0 && dname_valid(pend->query->pkt+12, pend->query->pkt_len-12)) { - char buf[LDNS_MAX_DOMAINLEN+1]; + char buf[LDNS_MAX_DOMAINLEN]; dname_str(pend->query->pkt+12, buf); verbose(VERB_CLIENT, "reuse_move_writewait_away current %s %d bytes were written", buf, (int)pend->c->tcp_write_byte_count); @@ -1079,7 +1079,7 @@ reuse_move_writewait_away(struct outside_network* outnet, if(verbosity >= VERB_CLIENT && w->pkt_len > 12+2+2 && LDNS_QDCOUNT(w->pkt) > 0 && dname_valid(w->pkt+12, w->pkt_len-12)) { - char buf[LDNS_MAX_DOMAINLEN+1]; + char buf[LDNS_MAX_DOMAINLEN]; dname_str(w->pkt+12, buf); verbose(VERB_CLIENT, "reuse_move_writewait_away item %s", buf); } @@ -2825,7 +2825,7 @@ serviced_perturb_qname(struct ub_randstate* rnd, uint8_t* qbuf, size_t len) lablen = *d++; } if(verbosity >= VERB_ALGO) { - char buf[LDNS_MAX_DOMAINLEN+1]; + char buf[LDNS_MAX_DOMAINLEN]; dname_str(qbuf+10, buf); verbose(VERB_ALGO, "qname perturbed to %s", buf); } diff --git a/services/rpz.c b/services/rpz.c index 3b92ee538..6658e89b7 100644 --- a/services/rpz.c +++ b/services/rpz.c @@ -666,7 +666,7 @@ rpz_insert_local_zones_trigger(struct local_zones* lz, uint8_t* dname, int newzone = 0; if(a == RPZ_INVALID_ACTION) { - char str[255+1]; + char str[LDNS_MAX_DOMAINLEN]; if(rrtype == LDNS_RR_TYPE_SOA || rrtype == LDNS_RR_TYPE_NS || rrtype == LDNS_RR_TYPE_DNAME || rrtype == LDNS_RR_TYPE_DNSKEY || @@ -739,7 +739,7 @@ rpz_insert_local_zones_trigger(struct local_zones* lz, uint8_t* dname, static void rpz_log_dname(char const* msg, uint8_t* dname, size_t dname_len) { - char buf[LDNS_MAX_DOMAINLEN+1]; + char buf[LDNS_MAX_DOMAINLEN]; (void)dname_len; dname_str(dname, buf); verbose(VERB_ALGO, "rpz: %s: <%s>", msg, buf); @@ -1062,7 +1062,7 @@ rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, if(a == RPZ_INVALID_ACTION || rpz_action_to_respip_action(a) == respip_invalid) { - char str[255+1]; + char str[LDNS_MAX_DOMAINLEN]; dname_str(dname, str); verbose(VERB_ALGO, "rpz: respip trigger, %s skipping unsupported action: %s", str, rpz_action_to_string(a)); @@ -1633,7 +1633,7 @@ log_rpz_apply(char* trigger, uint8_t* dname, struct addr_tree_node* addrnode, struct comm_reply* repinfo, struct module_qstate* ms, char* log_name) { char ip[128], txt[512], portstr[32]; - char dnamestr[LDNS_MAX_DOMAINLEN+1]; + char dnamestr[LDNS_MAX_DOMAINLEN]; uint16_t port = 0; if(dname) { dname_str(dname, dnamestr); @@ -2427,7 +2427,8 @@ rpz_delegation_point_zone_lookup(struct delegpt* dp, struct local_zones* zones, match->dname = nameserver->name; match->dname_len = nameserver->namelen; if(verbosity >= VERB_ALGO) { - char nm[255+1], zn[255+1]; + char nm[LDNS_MAX_DOMAINLEN]; + char zn[LDNS_MAX_DOMAINLEN]; dname_str(match->dname, nm); dname_str(z->name, zn); if(strcmp(nm, zn) != 0) @@ -2581,7 +2582,7 @@ struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* ms, } if(verbosity >= VERB_ALGO) { - char nm[255+1], zn[255+1]; + char nm[LDNS_MAX_DOMAINLEN], zn[LDNS_MAX_DOMAINLEN]; dname_str(is->qchase.qname, nm); dname_str(z->name, zn); if(strcmp(zn, nm) != 0) @@ -2758,7 +2759,7 @@ rpz_callback_from_worker_request(struct auth_zones* az, struct module_env* env, } if(verbosity >= VERB_ALGO) { - char nm[255+1], zn[255+1]; + char nm[LDNS_MAX_DOMAINLEN], zn[LDNS_MAX_DOMAINLEN]; dname_str(qinfo->qname, nm); dname_str(z->name, zn); if(strcmp(zn, nm) != 0) diff --git a/testcode/fake_event.c b/testcode/fake_event.c index 2f60b1381..def858706 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -1264,7 +1264,7 @@ struct serviced_query* outnet_serviced_query(struct outside_network* outnet, struct replay_runtime* runtime = (struct replay_runtime*)outnet->base; struct fake_pending* pend = (struct fake_pending*)calloc(1, sizeof(struct fake_pending)); - char z[256]; + char z[LDNS_MAX_DOMAINLEN]; log_assert(pend); log_nametypeclass(VERB_OPS, "pending serviced query", qinfo->qname, qinfo->qtype, qinfo->qclass); diff --git a/testcode/unitneg.c b/testcode/unitneg.c index 59c4e8dcc..d1d03fc99 100644 --- a/testcode/unitneg.c +++ b/testcode/unitneg.c @@ -53,7 +53,7 @@ static int negverbose = 0; /** debug printout of neg cache */ static void print_neg_cache(struct val_neg_cache* neg) { - char buf[1024]; + char buf[LDNS_MAX_DOMAINLEN]; struct val_neg_zone* z; struct val_neg_data* d; printf("neg_cache print\n"); diff --git a/testcode/unitzonemd.c b/testcode/unitzonemd.c index a8a168e33..63dc13eda 100644 --- a/testcode/unitzonemd.c +++ b/testcode/unitzonemd.c @@ -111,7 +111,7 @@ static void zonemd_generate_test(const char* zname, char* zfile, digestdup[i] = toupper((unsigned char)digestdup[i]); } if(verbosity >= VERB_ALGO) { - char zname[255+1]; + char zname[LDNS_MAX_DOMAINLEN]; dname_str(z->name, zname); printf("zonemd generated for %s in %s with " "scheme=%d hashalgo=%d\n", zname, z->zonefile, diff --git a/util/data/dname.h b/util/data/dname.h index 15e28bd97..6e4cf7ea3 100644 --- a/util/data/dname.h +++ b/util/data/dname.h @@ -242,11 +242,12 @@ void dname_print(FILE* out, struct sldns_buffer* pkt, uint8_t* dname); /** * Debug helper. Print dname to given string buffer (string buffer must - * be at least 255 chars + 1 for the 0, in printable form. + * be at least 255 chars, in printable form. * This may lose information (? for nonprintable characters, or & if * the name is too long, # for a bad label length). + * Upon return, the buffer will always have a terminating \0 value. * @param dname: uncompressed wireformat. - * @param str: buffer of 255+1 length. + * @param str: buffer of at least 255 length. */ void dname_str(uint8_t* dname, char* str); diff --git a/util/data/msgreply.c b/util/data/msgreply.c index 78e4fb1c3..167581c0c 100644 --- a/util/data/msgreply.c +++ b/util/data/msgreply.c @@ -967,13 +967,9 @@ log_reply_info(enum verbosity_value v, struct query_info *qinf, int cached, struct sldns_buffer *rmsg, struct sockaddr_storage* daddr, enum comm_point_type tp) { - char qname_buf[LDNS_MAX_DOMAINLEN+1]; char clientip_buf[128]; char rcode_buf[16]; - char type_buf[16]; - char class_buf[16]; char dest_buf[160]; - size_t pktlen; uint16_t rcode = FLAGS_GET_RCODE(sldns_buffer_read_u16_at(rmsg, 2)); if(verbosity < v) @@ -1022,6 +1018,10 @@ log_reply_info(enum verbosity_value v, struct query_info *qinf, else log_info("%s - - - %s - - -%s", clientip_buf, rcode_buf, dest_buf); } else { + char qname_buf[LDNS_MAX_DOMAINLEN]; + char type_buf[16]; + char class_buf[16]; + size_t pktlen; if(qinf->qname) dname_str(qinf->qname, qname_buf); else snprintf(qname_buf, sizeof(qname_buf), "null"); diff --git a/util/module.c b/util/module.c index 90a155b5e..d2c21b49d 100644 --- a/util/module.c +++ b/util/module.c @@ -135,7 +135,7 @@ char* errinf_to_str_bogus(struct module_qstate* qstate, struct regional* region) char* p = buf; size_t left = sizeof(buf); struct errinf_strlist* s; - char dname[LDNS_MAX_DOMAINLEN+1]; + char dname[LDNS_MAX_DOMAINLEN]; char t[16], c[16]; sldns_wire2str_type_buf(qstate->qinfo.qtype, t, sizeof(t)); sldns_wire2str_class_buf(qstate->qinfo.qclass, c, sizeof(c)); @@ -178,7 +178,7 @@ char* errinf_to_str_servfail(struct module_qstate* qstate) char* p = buf; size_t left = sizeof(buf); struct errinf_strlist* s; - char dname[LDNS_MAX_DOMAINLEN+1]; + char dname[LDNS_MAX_DOMAINLEN]; char t[16], c[16]; sldns_wire2str_type_buf(qstate->qinfo.qtype, t, sizeof(t)); sldns_wire2str_class_buf(qstate->qinfo.qclass, c, sizeof(c)); @@ -218,7 +218,7 @@ char* errinf_to_str_misc(struct module_qstate* qstate) void errinf_rrset(struct module_qstate* qstate, struct ub_packed_rrset_key *rr) { char buf[1024]; - char dname[LDNS_MAX_DOMAINLEN+1]; + char dname[LDNS_MAX_DOMAINLEN]; char t[16], c[16]; if((qstate->env->cfg->val_log_level < 2 && !qstate->env->cfg->log_servfail) || !rr) return; @@ -232,7 +232,7 @@ void errinf_rrset(struct module_qstate* qstate, struct ub_packed_rrset_key *rr) void errinf_dname(struct module_qstate* qstate, const char* str, uint8_t* dname) { char b[1024]; - char buf[LDNS_MAX_DOMAINLEN+1]; + char buf[LDNS_MAX_DOMAINLEN]; if((qstate->env->cfg->val_log_level < 2 && !qstate->env->cfg->log_servfail) || !str || !dname) return; dname_str(dname, buf); diff --git a/util/net_help.c b/util/net_help.c index 96b2b19a0..fbae91d9c 100644 --- a/util/net_help.c +++ b/util/net_help.c @@ -544,7 +544,7 @@ void log_nametypeclass(enum verbosity_value v, const char* str, uint8_t* name, uint16_t type, uint16_t dclass) { - char buf[LDNS_MAX_DOMAINLEN+1]; + char buf[LDNS_MAX_DOMAINLEN]; char t[12], c[12]; const char *ts, *cs; if(verbosity < v) @@ -575,7 +575,7 @@ log_nametypeclass(enum verbosity_value v, const char* str, uint8_t* name, void log_query_in(const char* str, uint8_t* name, uint16_t type, uint16_t dclass) { - char buf[LDNS_MAX_DOMAINLEN+1]; + char buf[LDNS_MAX_DOMAINLEN]; char t[12], c[12]; const char *ts, *cs; dname_str(name, buf); @@ -608,7 +608,7 @@ void log_name_addr(enum verbosity_value v, const char* str, uint8_t* zone, { uint16_t port; const char* family = "unknown_family "; - char namebuf[LDNS_MAX_DOMAINLEN+1]; + char namebuf[LDNS_MAX_DOMAINLEN]; char dest[100]; int af = (int)((struct sockaddr_in*)addr)->sin_family; void* sinaddr = &((struct sockaddr_in*)addr)->sin_addr; diff --git a/validator/autotrust.c b/validator/autotrust.c index 36cdf3e0a..e28d728fd 100644 --- a/validator/autotrust.c +++ b/validator/autotrust.c @@ -353,7 +353,7 @@ autr_tp_create(struct val_anchors* anchors, uint8_t* own, size_t own_len, lock_basic_lock(&anchors->lock); if(!rbtree_insert(anchors->tree, &tp->node)) { - char buf[LDNS_MAX_DOMAINLEN+1]; + char buf[LDNS_MAX_DOMAINLEN]; lock_basic_unlock(&anchors->lock); dname_str(tp->name, buf); log_err("trust anchor for '%s' presented twice", buf); @@ -363,7 +363,7 @@ autr_tp_create(struct val_anchors* anchors, uint8_t* own, size_t own_len, return NULL; } if(!rbtree_insert(&anchors->autr->probe, &tp->autr->pnode)) { - char buf[LDNS_MAX_DOMAINLEN+1]; + char buf[LDNS_MAX_DOMAINLEN]; (void)rbtree_delete(anchors->tree, tp); lock_basic_unlock(&anchors->lock); dname_str(tp->name, buf); @@ -2288,7 +2288,9 @@ static void autr_debug_print_tp(struct trust_anchor* tp) { struct autr_ta* ta; - char buf[257]; + /* Note: buf is also used for autr_ctime_r but that only needs a size + * of 26, so LDNS_MAX_DOMAINLEN is enough. */ + char buf[LDNS_MAX_DOMAINLEN]; if(!tp->autr) return; dname_str(tp->name, buf); diff --git a/validator/val_anchor.c b/validator/val_anchor.c index 8466a8923..7a5256264 100644 --- a/validator/val_anchor.c +++ b/validator/val_anchor.c @@ -1018,7 +1018,7 @@ anchors_assemble_rrsets(struct val_anchors* anchors) ta->name, LDNS_RR_TYPE_DNSKEY, ta->dclass); } if(nods == ta->numDS && nokey == ta->numDNSKEY) { - char b[257]; + char b[LDNS_MAX_DOMAINLEN]; dname_str(ta->name, b); log_warn("trust anchor %s has no supported algorithms," " the anchor is ignored (check if you need to" diff --git a/validator/validator.c b/validator/validator.c index 1a51dc270..7bfc46737 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -210,7 +210,7 @@ val_init(struct module_env* env, int id) struct trust_anchor* anchor = anchors_find_any_noninsecure( env->anchors); if(anchor) { - char b[LDNS_MAX_DOMAINLEN+2]; + 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); From c55490c1e6c1b1206457e1c1aae2dc418a60a873 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 2 Dec 2024 12:28:11 +0100 Subject: [PATCH 011/218] - Fix #1193: log-servfail fails to log host SERVFAIL responses in Unbound 1.19.2 on Ubuntu 24.04.1 LTS, by not considering cached failures when trying to reply with expired data. --- services/mesh.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/mesh.c b/services/mesh.c index 5b9a02ae4..18a53b4a5 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -2203,8 +2203,13 @@ mesh_serve_expired_callback(void* arg) qstate->serve_expired_data->get_cached_answer)); msg = (*qstate->serve_expired_data->get_cached_answer)(qstate, lookup_qinfo, &is_expired); - if(!msg) + if(!msg || (FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NOERROR + && FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NXDOMAIN + && FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_YXDOMAIN)) { + /* We don't care for cached failure answers at this + * stage. */ return; + } /* Reset these in case we pass a second time from here. */ encode_rep = msg->rep; memset(&actinfo, 0, sizeof(actinfo)); From c124f67f33534a9cc7a52d951865872e887a92cc Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 2 Dec 2024 12:30:11 +0100 Subject: [PATCH 012/218] - For #1193, introduce log-servfail.tdir and cleanup the log-servfail setting from other tests. --- testdata/iter_failreply.rpl | 1 - testdata/iter_scrub_rr_length.rpl | 1 - testdata/log_servfail.tdir/log_servfail.conf | 27 +++++++++++ testdata/log_servfail.tdir/log_servfail.dsc | 16 +++++++ testdata/log_servfail.tdir/log_servfail.post | 10 ++++ testdata/log_servfail.tdir/log_servfail.pre | 21 +++++++++ testdata/log_servfail.tdir/log_servfail.test | 47 +++++++++++++++++++ testdata/rpz_val_block.rpl | 1 - testdata/serve_expired_0ttl_nodata.rpl | 1 - testdata/serve_expired_0ttl_nxdomain.rpl | 1 - testdata/serve_expired_0ttl_servfail.rpl | 1 - testdata/serve_expired_cached_servfail.rpl | 1 - .../serve_expired_cached_servfail_refresh.rpl | 1 - .../serve_expired_client_timeout_servfail.rpl | 1 - testdata/val_failure_dnskey.rpl | 1 - testdata/val_scrub_rr_length.rpl | 1 - 16 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 testdata/log_servfail.tdir/log_servfail.conf create mode 100644 testdata/log_servfail.tdir/log_servfail.dsc create mode 100644 testdata/log_servfail.tdir/log_servfail.post create mode 100644 testdata/log_servfail.tdir/log_servfail.pre create mode 100644 testdata/log_servfail.tdir/log_servfail.test diff --git a/testdata/iter_failreply.rpl b/testdata/iter_failreply.rpl index 393714196..e8ad4dd26 100644 --- a/testdata/iter_failreply.rpl +++ b/testdata/iter_failreply.rpl @@ -3,7 +3,6 @@ server: target-fetch-policy: "0 0 0 0 0" qname-minimisation: "no" minimal-responses: no - log-servfail: yes stub-zone: name: "." diff --git a/testdata/iter_scrub_rr_length.rpl b/testdata/iter_scrub_rr_length.rpl index 2ef73c2fe..ee7579f9c 100644 --- a/testdata/iter_scrub_rr_length.rpl +++ b/testdata/iter_scrub_rr_length.rpl @@ -5,7 +5,6 @@ server: minimal-responses: no rrset-roundrobin: no ede: yes - log-servfail: yes stub-zone: name: "." diff --git a/testdata/log_servfail.tdir/log_servfail.conf b/testdata/log_servfail.tdir/log_servfail.conf new file mode 100644 index 000000000..2d7c34e68 --- /dev/null +++ b/testdata/log_servfail.tdir/log_servfail.conf @@ -0,0 +1,27 @@ +server: + verbosity: 0 + use-syslog: no + directory: "" + pidfile: "unbound.pid" + chroot: "" + username: "" + do-not-query-localhost: no + use-caps-for-id: no + port: @SERVER_PORT@ + interface: 127.0.0.1 + outbound-msg-retry: 0 + + log-servfail: yes + +forward-zone: + name: "a.servfail" + forward-addr: 127.0.0.1@@SERVER_PORT@ + +forward-zone: + name: "b.servfail" + forward-addr: 127.0.0.1@@SERVER_PORT@ + +remote-control: + control-enable: yes + control-port: @CONTROL_PORT@ + control-use-cert: no diff --git a/testdata/log_servfail.tdir/log_servfail.dsc b/testdata/log_servfail.tdir/log_servfail.dsc new file mode 100644 index 000000000..cf4f455aa --- /dev/null +++ b/testdata/log_servfail.tdir/log_servfail.dsc @@ -0,0 +1,16 @@ +BaseName: log_servfail +Version: 1.0 +Description: Check the log_servfail option +CreationDate: Fri 29 Nov 11:00:00 CEST 2024 +Maintainer: +Category: +Component: +CmdDepends: +Depends: +Help: +Pre: log_servfail.pre +Post: log_servfail.post +Test: log_servfail.test +AuxFiles: +Passed: +Failure: diff --git a/testdata/log_servfail.tdir/log_servfail.post b/testdata/log_servfail.tdir/log_servfail.post new file mode 100644 index 000000000..a7bd0e88f --- /dev/null +++ b/testdata/log_servfail.tdir/log_servfail.post @@ -0,0 +1,10 @@ +# #-- log_servfail.post --# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# source the test var file when it's there +[ -f .tpkg.var.test ] && source .tpkg.var.test +# +# do your teardown here +. ../common.sh +kill_from_pidfile "unbound.pid" +cat unbound.log diff --git a/testdata/log_servfail.tdir/log_servfail.pre b/testdata/log_servfail.tdir/log_servfail.pre new file mode 100644 index 000000000..540594808 --- /dev/null +++ b/testdata/log_servfail.tdir/log_servfail.pre @@ -0,0 +1,21 @@ +# #-- log_servfail.pre--# +PRE="../.." +. ../common.sh + +get_random_port 2 +SERVER_PORT=$RND_PORT +CONTROL_PORT=$(($RND_PORT + 1)) +echo "SERVER_PORT=$SERVER_PORT" >> .tpkg.var.test +echo "CONTROL_PORT=$CONTROL_PORT" >> .tpkg.var.test + +# make config file +sed \ + -e 's/@SERVER_PORT\@/'$SERVER_PORT'/' \ + -e 's/@CONTROL_PORT\@/'$CONTROL_PORT'/' \ + < log_servfail.conf > ub.conf + +# start unbound in the background +$PRE/unbound -d -c ub.conf > unbound.log 2>&1 & + +cat .tpkg.var.test +wait_unbound_up unbound.log diff --git a/testdata/log_servfail.tdir/log_servfail.test b/testdata/log_servfail.tdir/log_servfail.test new file mode 100644 index 000000000..a6156ba04 --- /dev/null +++ b/testdata/log_servfail.tdir/log_servfail.test @@ -0,0 +1,47 @@ +# #-- cookie_file.test --# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# use .tpkg.var.test for in test variable passing +[ -f .tpkg.var.test ] && source .tpkg.var.test +PRE="../.." +. ../common.sh + +outfile=dig.out + +teststep "Check if log-servfail logs to output for iterator error" +dig a.servfail @127.0.0.1 -p $SERVER_PORT > $outfile +if ! grep "SERVFAIL" $outfile +then + cat $outfile + echo "Did not get a SERVFAIL response" + exit 1 +fi +if ! grep "SERVFAIL $outfile +if ! grep "SERVFAIL" $outfile +then + cat $outfile + echo "Did not get a SERVFAIL response" + exit 1 +fi +if ! grep "SERVFAIL Date: Tue, 3 Dec 2024 11:58:06 +0100 Subject: [PATCH 013/218] Changelog entry for #1189, #1197: - Merge #1189: Fix the dname_str method to cause conversion errors when the domain name length is 255. - Merge #1197: dname_str() fixes. --- doc/Changelog | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 0403e0c10..d96ae8020 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,8 @@ +3 December 2024: Yorgos + - Merge #1189: Fix the dname_str method to cause conversion errors + when the domain name length is 255. + - Merge #1197: dname_str() fixes. + 22 November 2024: Yorgos - Fix #1175: serve-expired does not adhere to secure-by-default principle. The default value of serve-expired-client-timeout From 9de159b96bb218ad0613055de77302d5aad15b0d Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 3 Dec 2024 13:09:51 +0100 Subject: [PATCH 014/218] - For #1175, the default value of serve-expired-ttl is set to 86400 (1 day) as suggested by RFC8767. --- doc/Changelog | 2 ++ doc/example.conf.in | 2 +- doc/unbound.conf.5.in | 9 +++++---- util/config_file.c | 2 +- util/data/msgreply.c | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index d96ae8020..331d03c55 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,8 @@ - Merge #1189: Fix the dname_str method to cause conversion errors when the domain name length is 255. - Merge #1197: dname_str() fixes. + - For #1175, the default value of serve-expired-ttl is set to 86400 + (1 day) as suggested by RFC8767. 22 November 2024: Yorgos - Fix #1175: serve-expired does not adhere to secure-by-default diff --git a/doc/example.conf.in b/doc/example.conf.in index e0ee39ad4..33e12f180 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -737,7 +737,7 @@ server: # # Limit serving of expired responses to configured seconds after # expiration. 0 disables the limit. - # serve-expired-ttl: 0 + # serve-expired-ttl: 86400 # # Set the TTL of expired records to the serve-expired-ttl value after a # failed attempt to retrieve the record from upstream. This makes sure diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index cf6f14915..4d1fb13f8 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -1408,10 +1408,11 @@ out or is taking more than serve\-expired\-client\-timeout to resolve. Default is "no". .TP .B serve\-expired\-ttl: \fI -Limit serving of expired responses to configured seconds after expiration. 0 -disables the limit. This option only applies when \fBserve\-expired\fR is -enabled. A suggested value per RFC 8767 is between -86400 (1 day) and 259200 (3 days). The default is 0. +Limit serving of expired responses to configured seconds after expiration. +0 disables the limit. +This option only applies when \fBserve\-expired\fR is enabled. +A suggested value per RFC 8767 is between 86400 (1 day) and 259200 (3 days). +The default is 86400. .TP .B serve\-expired\-ttl\-reset: \fI Set the TTL of expired records to the \fBserve\-expired\-ttl\fR value after a diff --git a/util/config_file.c b/util/config_file.c index c1c55c529..58567dccc 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -280,7 +280,7 @@ config_create(void) cfg->ignore_cd = 0; cfg->disable_edns_do = 0; cfg->serve_expired = 0; - cfg->serve_expired_ttl = 0; + cfg->serve_expired_ttl = 86400; cfg->serve_expired_ttl_reset = 0; cfg->serve_expired_reply_ttl = 30; cfg->serve_expired_client_timeout = 1800; diff --git a/util/data/msgreply.c b/util/data/msgreply.c index 167581c0c..530eee701 100644 --- a/util/data/msgreply.c +++ b/util/data/msgreply.c @@ -66,7 +66,7 @@ time_t MIN_NEG_TTL = 0; /** If we serve expired entries and prefetch them */ int SERVE_EXPIRED = 0; /** Time to serve records after expiration */ -time_t SERVE_EXPIRED_TTL = 0; +time_t SERVE_EXPIRED_TTL = 86400; /** Reset serve expired TTL after failed update attempt */ time_t SERVE_EXPIRED_TTL_RESET = 0; /** TTL to use for expired records */ From be92752368bdf01313f697e12299e049c634fca6 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 3 Dec 2024 14:05:12 +0100 Subject: [PATCH 015/218] - Merge #1198: Fix log-servfail with serve expired and no useful cache contents. --- doc/Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 331d03c55..fdcf53f3c 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -4,6 +4,8 @@ - Merge #1197: dname_str() fixes. - For #1175, the default value of serve-expired-ttl is set to 86400 (1 day) as suggested by RFC8767. + - Merge #1198: Fix log-servfail with serve expired and no useful cache + contents. 22 November 2024: Yorgos - Fix #1175: serve-expired does not adhere to secure-by-default From b4a9c8bb0573b565375db5efd8e7f849667fa881 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 3 Dec 2024 14:10:17 +0100 Subject: [PATCH 016/218] - Safeguard alias loop while looking in the cache for expired answers. --- doc/Changelog | 1 + services/mesh.c | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index fdcf53f3c..5e625dc97 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -6,6 +6,7 @@ (1 day) as suggested by RFC8767. - Merge #1198: Fix log-servfail with serve expired and no useful cache contents. + - Safeguard alias loop while looking in the cache for expired answers. 22 November 2024: Yorgos - Fix #1175: serve-expired does not adhere to secure-by-default diff --git a/services/mesh.c b/services/mesh.c index 18a53b4a5..a25094d12 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -2183,7 +2183,7 @@ mesh_serve_expired_callback(void* arg) struct timeval tv = {0, 0}; int must_validate = (!(qstate->query_flags&BIT_CD) || qstate->env->cfg->ignore_cd) && qstate->env->need_to_validate; - int i = 0; + int i = 0, for_count; int is_expired; if(!qstate->serve_expired_data) return; verbose(VERB_ALGO, "Serve expired: Trying to reply with expired data"); @@ -2196,9 +2196,10 @@ mesh_serve_expired_callback(void* arg) "Serve expired: Not allowed to look into cache for stale"); return; } - /* The following while is used instead of the `goto lookup_cache` - * like in the worker. */ - while(1) { + /* The following for is used instead of the `goto lookup_cache` + * like in the worker. This loop should get max 2 passes if we need to + * do any aliasing. */ + for(for_count = 0; for_count < 2; for_count++) { fptr_ok(fptr_whitelist_serve_expired_lookup( qstate->serve_expired_data->get_cached_answer)); msg = (*qstate->serve_expired_data->get_cached_answer)(qstate, From 61d7250b96577731be5e000d6e7eea9acbf26413 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 3 Dec 2024 14:20:33 +0100 Subject: [PATCH 017/218] Create the SSL_CTX for QUIC before chroot and privilege drop (#1187) Fixes #1185 by creating the SSL_CTX for QUIC before chroot and privilege drop, just like the other SSL_CTX creations. --------- Co-authored-by: Wouter Wijngaards --- daemon/daemon.c | 3 ++ daemon/daemon.h | 2 ++ daemon/unbound.c | 13 +++++-- daemon/worker.c | 4 +-- services/listen_dnsport.c | 71 +++++++++++++++++++-------------------- services/listen_dnsport.h | 21 +++++++----- testcode/fake_event.c | 5 ++- testcode/testbound.c | 6 ++++ util/net_help.h | 2 +- util/netevent.c | 71 +++++---------------------------------- util/netevent.h | 13 ++----- winrc/win_svc.c | 5 +++ 12 files changed, 90 insertions(+), 126 deletions(-) diff --git a/daemon/daemon.c b/daemon/daemon.c index 1c8272b14..9e1d5d4f7 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -956,6 +956,9 @@ daemon_delete(struct daemon* daemon) listen_sslctx_delete_ticket_keys(); SSL_CTX_free((SSL_CTX*)daemon->listen_sslctx); SSL_CTX_free((SSL_CTX*)daemon->connect_sslctx); +#endif +#ifdef HAVE_NGTCP2 + SSL_CTX_free((SSL_CTX*)daemon->quic_sslctx); #endif free(daemon); /* lex cleanup */ diff --git a/daemon/daemon.h b/daemon/daemon.h index fc1bde713..f93887e3d 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -99,6 +99,8 @@ struct daemon { struct daemon_remote* rc; /** ssl context for listening to dnstcp over ssl, and connecting ssl */ void* listen_sslctx, *connect_sslctx; + /** ssl context for listening to quic */ + void* quic_sslctx; /** num threads allocated */ int num; /** num threads allocated in the previous config or 0 at first */ diff --git a/daemon/unbound.c b/daemon/unbound.c index 306fe6caf..f394fbde3 100644 --- a/daemon/unbound.c +++ b/daemon/unbound.c @@ -494,8 +494,9 @@ perform_setup(struct daemon* daemon, struct config_file* cfg, int debug_mode, fatal_exit("could not set up remote-control"); if(cfg->ssl_service_key && cfg->ssl_service_key[0]) { if(!(daemon->listen_sslctx = listen_sslctx_create( - cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) + cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) { fatal_exit("could not set up listen SSL_CTX"); + } if(cfg->tls_ciphers && cfg->tls_ciphers[0]) { if (!SSL_CTX_set_cipher_list(daemon->listen_sslctx, cfg->tls_ciphers)) { fatal_exit("failed to set tls-cipher %s", cfg->tls_ciphers); @@ -507,18 +508,24 @@ perform_setup(struct daemon* daemon, struct config_file* cfg, int debug_mode, fatal_exit("failed to set tls-ciphersuites %s", cfg->tls_ciphersuites); } } -#endif +#endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */ if(cfg->tls_session_ticket_keys.first && cfg->tls_session_ticket_keys.first->str[0] != 0) { if(!listen_sslctx_setup_ticket_keys(daemon->listen_sslctx, cfg->tls_session_ticket_keys.first)) { fatal_exit("could not set session ticket SSL_CTX"); } } +#ifdef HAVE_NGTCP2 + if(!(daemon->quic_sslctx = quic_sslctx_create( + cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) { + fatal_exit("could not set up quic SSL_CTX"); + } +#endif /* HAVE_NGTCP2 */ } if(!(daemon->connect_sslctx = connect_sslctx_create(NULL, NULL, cfg->tls_cert_bundle, cfg->tls_win_cert))) fatal_exit("could not set up connect SSL_CTX"); -#endif +#endif /* HAVE_SSL */ /* init syslog (as root) if needed, before daemonize, otherwise * a fork error could not be printed since daemonize closed stderr.*/ diff --git a/daemon/worker.c b/daemon/worker.c index f44cd761a..413abdb87 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -2174,9 +2174,9 @@ worker_init(struct worker* worker, struct config_file *cfg, cfg->harden_large_queries, cfg->http_max_streams, cfg->http_endpoint, cfg->http_notls_downstream, worker->daemon->tcl, worker->daemon->listen_sslctx, + worker->daemon->quic_sslctx, dtenv, worker->daemon->doq_table, worker->env.rnd, - cfg->ssl_service_key, cfg->ssl_service_pem, cfg, - worker_handle_request, worker); + cfg, worker_handle_request, worker); if(!worker->front) { log_err("could not create listening sockets"); worker_delete(worker); diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index 5dbac3650..bdd14b04a 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -1523,9 +1523,9 @@ listen_create(struct comm_base* base, struct listen_port* ports, size_t bufsize, int tcp_accept_count, int tcp_idle_timeout, int harden_large_queries, uint32_t http_max_streams, char* http_endpoint, int http_notls, struct tcl_list* tcp_conn_limit, - void* sslctx, struct dt_env* dtenv, struct doq_table* doq_table, - struct ub_randstate* rnd, const char* ssl_service_key, - const char* ssl_service_pem, struct config_file* cfg, + void* sslctx, void* quic_sslctx, struct dt_env* dtenv, + struct doq_table* doq_table, + struct ub_randstate* rnd,struct config_file* cfg, comm_point_callback_type* cb, void *cb_arg) { struct listen_dnsport* front = (struct listen_dnsport*) @@ -1558,8 +1558,7 @@ listen_create(struct comm_base* base, struct listen_port* ports, #endif cp = comm_point_create_doq(base, ports->fd, front->udp_buff, cb, cb_arg, ports->socket, - doq_table, rnd, ssl_service_key, - ssl_service_pem, cfg); + doq_table, rnd, quic_sslctx, cfg); } else if(ports->ftype == listen_type_tcp || ports->ftype == listen_type_tcp_dnscrypt) { cp = comm_point_create_tcp(base, ports->fd, @@ -1620,10 +1619,13 @@ listen_create(struct comm_base* base, struct listen_port* ports, (ports->ftype == listen_type_udpancil) || (ports->ftype == listen_type_tcp_dnscrypt) || (ports->ftype == listen_type_udp_dnscrypt) || - (ports->ftype == listen_type_udpancil_dnscrypt)) + (ports->ftype == listen_type_udpancil_dnscrypt)) { cp->ssl = NULL; - else + } else if(ports->ftype == listen_type_doq) { + cp->ssl = quic_sslctx; + } else { cp->ssl = sslctx; + } cp->dtenv = dtenv; cp->do_not_close = 1; #ifdef USE_DNSCRYPT @@ -4598,10 +4600,9 @@ doq_alpn_select_cb(SSL* ATTR_UNUSED(ssl), const unsigned char** out, return SSL_TLSEXT_ERR_ALERT_FATAL; } -/** create new tls session for server doq connection */ -static SSL_CTX* -doq_ctx_server_setup(struct doq_server_socket* doq_socket) +void* quic_sslctx_create(char* key, char* pem, char* verifypem) { +#ifdef HAVE_NGTCP2 char* sid_ctx = "unbound server"; #ifndef HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_SERVER_CONTEXT SSL_QUIC_METHOD* quic_method; @@ -4611,6 +4612,16 @@ doq_ctx_server_setup(struct doq_server_socket* doq_socket) log_crypto_err("Could not SSL_CTX_new"); return NULL; } + if(!key || key[0] == 0) { + log_err("doq: error, no tls-service-key file specified"); + SSL_CTX_free(ctx); + return NULL; + } + if(!pem || pem[0] == 0) { + log_err("doq: error, no tls-service-pem file specified"); + SSL_CTX_free(ctx); + return NULL; + } SSL_CTX_set_options(ctx, (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | SSL_OP_SINGLE_ECDH_USE | @@ -4623,43 +4634,37 @@ doq_ctx_server_setup(struct doq_server_socket* doq_socket) SSL_CTX_set_alpn_select_cb(ctx, doq_alpn_select_cb, NULL); #endif SSL_CTX_set_default_verify_paths(ctx); - if(!SSL_CTX_use_certificate_chain_file(ctx, - doq_socket->ssl_service_pem)) { - log_err("doq: error for cert file: %s", - doq_socket->ssl_service_pem); + if(!SSL_CTX_use_certificate_chain_file(ctx, pem)) { + log_err("doq: error for cert file: %s", pem); log_crypto_err("doq: error in " "SSL_CTX_use_certificate_chain_file"); SSL_CTX_free(ctx); return NULL; } - if(!SSL_CTX_use_PrivateKey_file(ctx, doq_socket->ssl_service_key, - SSL_FILETYPE_PEM)) { - log_err("doq: error for private key file: %s", - doq_socket->ssl_service_key); + if(!SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM)) { + log_err("doq: error for private key file: %s", key); log_crypto_err("doq: error in SSL_CTX_use_PrivateKey_file"); SSL_CTX_free(ctx); return NULL; } if(!SSL_CTX_check_private_key(ctx)) { - log_err("doq: error for key file: %s", - doq_socket->ssl_service_key); + log_err("doq: error for key file: %s", key); log_crypto_err("doq: error in SSL_CTX_check_private_key"); SSL_CTX_free(ctx); return NULL; } SSL_CTX_set_session_id_context(ctx, (void*)sid_ctx, strlen(sid_ctx)); - if(doq_socket->ssl_verify_pem && doq_socket->ssl_verify_pem[0]) { - if(!SSL_CTX_load_verify_locations(ctx, - doq_socket->ssl_verify_pem, NULL)) { + if(verifypem && verifypem[0]) { + if(!SSL_CTX_load_verify_locations(ctx, verifypem, NULL)) { log_err("doq: error for verify pem file: %s", - doq_socket->ssl_verify_pem); + verifypem); log_crypto_err("doq: error in " "SSL_CTX_load_verify_locations"); SSL_CTX_free(ctx); return NULL; } SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file( - doq_socket->ssl_verify_pem)); + verifypem)); SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER| SSL_VERIFY_CLIENT_ONCE| SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); @@ -4672,7 +4677,7 @@ doq_ctx_server_setup(struct doq_server_socket* doq_socket) SSL_CTX_free(ctx); return NULL; } -#else +#else /* HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_SERVER_CONTEXT */ /* The quic_method needs to remain valid during the SSL_CTX * lifetime, so we allocate it. It is freed with the * doq_server_socket. */ @@ -4690,6 +4695,10 @@ doq_ctx_server_setup(struct doq_server_socket* doq_socket) SSL_CTX_set_quic_method(ctx, doq_socket->quic_method); #endif return ctx; +#else /* HAVE_NGTCP2 */ + (void)key; (void)pem; (void)verifypem; + return NULL; +#endif /* HAVE_NGTCP2 */ } /** Get the ngtcp2_conn from ssl userdata of type ngtcp2_conn_ref */ @@ -4720,16 +4729,6 @@ doq_ssl_server_setup(SSL_CTX* ctx, struct doq_conn* conn) return ssl; } -/** setup the doq_socket server tls context */ -int -doq_socket_setup_ctx(struct doq_server_socket* doq_socket) -{ - doq_socket->ctx = doq_ctx_server_setup(doq_socket); - if(!doq_socket->ctx) - return 0; - return 1; -} - int doq_conn_setup(struct doq_conn* conn, uint8_t* scid, size_t scidlen, uint8_t* ocid, size_t ocidlen, const uint8_t* token, size_t tokenlen) diff --git a/services/listen_dnsport.h b/services/listen_dnsport.h index c29f4d72b..629f20bd4 100644 --- a/services/listen_dnsport.h +++ b/services/listen_dnsport.h @@ -195,11 +195,10 @@ int resolve_interface_names(char** ifs, int num_ifs, * @param http_notls: no TLS for http downstream * @param tcp_conn_limit: TCP connection limit info. * @param sslctx: nonNULL if ssl context. + * @param quic_sslctx: nonNULL if quic ssl context. * @param dtenv: nonNULL if dnstap enabled. * @param doq_table: the doq connection table, with shared information. * @param rnd: random state. - * @param ssl_service_key: the SSL service key file. - * @param ssl_service_pem: the SSL service pem file. * @param cfg: config file struct. * @param cb: callback function when a request arrives. It is passed * the packet and user argument. Return true to send a reply. @@ -211,9 +210,9 @@ listen_create(struct comm_base* base, struct listen_port* ports, size_t bufsize, int tcp_accept_count, int tcp_idle_timeout, int harden_large_queries, uint32_t http_max_streams, char* http_endpoint, int http_notls, struct tcl_list* tcp_conn_limit, - void* sslctx, struct dt_env* dtenv, struct doq_table* doq_table, - struct ub_randstate* rnd, const char* ssl_service_key, - const char* ssl_service_pem, struct config_file* cfg, + void* sslctx, void* quic_sslctx, struct dt_env* dtenv, + struct doq_table* doq_table, + struct ub_randstate* rnd,struct config_file* cfg, comm_point_callback_type* cb, void *cb_arg); /** @@ -512,6 +511,15 @@ struct doq_table { size_t current_size; }; +/** + * create SSL context for QUIC + * @param key: private key file. + * @param pem: public key cert. + * @param verifypem: if nonNULL, verifylocation file. + * return SSL_CTX* or NULL on failure (logged). + */ +void* quic_sslctx_create(char* key, char* pem, char* verifypem); + /** create doq table */ struct doq_table* doq_table_create(struct config_file* cfg, struct ub_randstate* rnd); @@ -712,9 +720,6 @@ int doq_timer_cmp(const void* key1, const void* key2); /** compare function of doq_stream */ int doq_stream_cmp(const void* key1, const void* key2); -/** setup the doq_socket server tls context */ -int doq_socket_setup_ctx(struct doq_server_socket* doq_socket); - /** setup the doq connection callbacks, and settings. */ int doq_conn_setup(struct doq_conn* conn, uint8_t* scid, size_t scidlen, uint8_t* ocid, size_t ocidlen, const uint8_t* token, size_t tokenlen); diff --git a/testcode/fake_event.c b/testcode/fake_event.c index def858706..be61ed5c6 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -938,11 +938,10 @@ listen_create(struct comm_base* base, struct listen_port* ATTR_UNUSED(ports), char* ATTR_UNUSED(http_endpoint), int ATTR_UNUSED(http_notls), struct tcl_list* ATTR_UNUSED(tcp_conn_limit), - void* ATTR_UNUSED(sslctx), struct dt_env* ATTR_UNUSED(dtenv), + void* ATTR_UNUSED(sslctx), void* ATTR_UNUSED(quic_ssl), + struct dt_env* ATTR_UNUSED(dtenv), struct doq_table* ATTR_UNUSED(table), struct ub_randstate* ATTR_UNUSED(rnd), - const char* ATTR_UNUSED(ssl_service_key), - const char* ATTR_UNUSED(ssl_service_pem), struct config_file* ATTR_UNUSED(cfg), comm_point_callback_type* cb, void *cb_arg) { diff --git a/testcode/testbound.c b/testcode/testbound.c index 442e23434..f9ea86e69 100644 --- a/testcode/testbound.c +++ b/testcode/testbound.c @@ -602,6 +602,12 @@ void listen_desetup_locks(void) } #ifdef HAVE_NGTCP2 +void* quic_sslctx_create(char* ATTR_UNUSED(key), char* ATTR_UNUSED(pem), + char* ATTR_UNUSED(verifypem)) +{ + return NULL; +} + void comm_point_doq_callback(int ATTR_UNUSED(fd), short ATTR_UNUSED(event), void* ATTR_UNUSED(arg)) { diff --git a/util/net_help.h b/util/net_help.h index eba38cee0..4365c9a6a 100644 --- a/util/net_help.h +++ b/util/net_help.h @@ -488,7 +488,7 @@ int listen_sslctx_setup(void* ctxt); */ void listen_sslctx_setup_2(void* ctxt); -/** +/** * create SSL listen context * @param key: private key file. * @param pem: public key cert. diff --git a/util/netevent.c b/util/netevent.c index 9181d3e4a..8f64278c3 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -2687,8 +2687,8 @@ comm_point_doq_callback(int fd, short event, void* arg) /** create new doq server socket structure */ static struct doq_server_socket* doq_server_socket_create(struct doq_table* table, struct ub_randstate* rnd, - const char* ssl_service_key, const char* ssl_service_pem, - struct comm_point* c, struct comm_base* base, struct config_file* cfg) + const void* quic_sslctx, struct comm_point* c, struct comm_base* base, + struct config_file* cfg) { size_t doq_buffer_size = 4096; /* bytes buffer size, for one packet. */ struct doq_server_socket* doq_socket; @@ -2699,69 +2699,29 @@ doq_server_socket_create(struct doq_table* table, struct ub_randstate* rnd, doq_socket->table = table; doq_socket->rnd = rnd; doq_socket->validate_addr = 1; - if(ssl_service_key == NULL || ssl_service_key[0]==0) { - log_err("doq server socket create: no tls-service-key"); - free(doq_socket); - return NULL; - } - if(ssl_service_pem == NULL || ssl_service_pem[0]==0) { - log_err("doq server socket create: no tls-service-pem"); - free(doq_socket); - return NULL; - } - doq_socket->ssl_service_key = strdup(ssl_service_key); - if(!doq_socket->ssl_service_key) { - free(doq_socket); - return NULL; - } - doq_socket->ssl_service_pem = strdup(ssl_service_pem); - if(!doq_socket->ssl_service_pem) { - free(doq_socket->ssl_service_key); - free(doq_socket); - return NULL; - } - doq_socket->ssl_verify_pem = NULL; /* the doq_socket has its own copy of the static secret, as * well as other config values, so that they do not need table.lock */ doq_socket->static_secret_len = table->static_secret_len; doq_socket->static_secret = memdup(table->static_secret, table->static_secret_len); if(!doq_socket->static_secret) { - free(doq_socket->ssl_service_key); - free(doq_socket->ssl_service_pem); - free(doq_socket->ssl_verify_pem); - free(doq_socket); - return NULL; - } - if(!doq_socket_setup_ctx(doq_socket)) { - free(doq_socket->ssl_service_key); - free(doq_socket->ssl_service_pem); - free(doq_socket->ssl_verify_pem); - free(doq_socket->static_secret); free(doq_socket); return NULL; } + doq_socket->ctx = (SSL_CTX*)quic_sslctx; doq_socket->idle_timeout = table->idle_timeout; doq_socket->sv_scidlen = table->sv_scidlen; doq_socket->cp = c; doq_socket->pkt_buf = sldns_buffer_new(doq_buffer_size); if(!doq_socket->pkt_buf) { - free(doq_socket->ssl_service_key); - free(doq_socket->ssl_service_pem); - free(doq_socket->ssl_verify_pem); free(doq_socket->static_secret); - SSL_CTX_free(doq_socket->ctx); free(doq_socket); return NULL; } doq_socket->blocked_pkt = sldns_buffer_new( sldns_buffer_capacity(doq_socket->pkt_buf)); if(!doq_socket->pkt_buf) { - free(doq_socket->ssl_service_key); - free(doq_socket->ssl_service_pem); - free(doq_socket->ssl_verify_pem); free(doq_socket->static_secret); - SSL_CTX_free(doq_socket->ctx); sldns_buffer_free(doq_socket->pkt_buf); free(doq_socket); return NULL; @@ -2769,11 +2729,7 @@ doq_server_socket_create(struct doq_table* table, struct ub_randstate* rnd, doq_socket->blocked_paddr = calloc(1, sizeof(*doq_socket->blocked_paddr)); if(!doq_socket->blocked_paddr) { - free(doq_socket->ssl_service_key); - free(doq_socket->ssl_service_pem); - free(doq_socket->ssl_verify_pem); free(doq_socket->static_secret); - SSL_CTX_free(doq_socket->ctx); sldns_buffer_free(doq_socket->pkt_buf); sldns_buffer_free(doq_socket->blocked_pkt); free(doq_socket); @@ -2781,11 +2737,7 @@ doq_server_socket_create(struct doq_table* table, struct ub_randstate* rnd, } doq_socket->timer = comm_timer_create(base, doq_timer_cb, doq_socket); if(!doq_socket->timer) { - free(doq_socket->ssl_service_key); - free(doq_socket->ssl_service_pem); - free(doq_socket->ssl_verify_pem); free(doq_socket->static_secret); - SSL_CTX_free(doq_socket->ctx); sldns_buffer_free(doq_socket->pkt_buf); sldns_buffer_free(doq_socket->blocked_pkt); free(doq_socket->blocked_paddr); @@ -2805,13 +2757,9 @@ doq_server_socket_delete(struct doq_server_socket* doq_socket) if(!doq_socket) return; free(doq_socket->static_secret); - SSL_CTX_free(doq_socket->ctx); #ifndef HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_SERVER_CONTEXT free(doq_socket->quic_method); #endif - free(doq_socket->ssl_service_key); - free(doq_socket->ssl_service_pem); - free(doq_socket->ssl_verify_pem); sldns_buffer_free(doq_socket->pkt_buf); sldns_buffer_free(doq_socket->blocked_pkt); free(doq_socket->blocked_paddr); @@ -5862,8 +5810,8 @@ struct comm_point* comm_point_create_doq(struct comm_base *base, int fd, sldns_buffer* buffer, comm_point_callback_type* callback, void* callback_arg, struct unbound_socket* socket, struct doq_table* table, - struct ub_randstate* rnd, const char* ssl_service_key, - const char* ssl_service_pem, struct config_file* cfg) + struct ub_randstate* rnd, const void* quic_sslctx, + struct config_file* cfg) { #ifdef HAVE_NGTCP2 struct comm_point* c = (struct comm_point*)calloc(1, @@ -5900,15 +5848,13 @@ comm_point_create_doq(struct comm_base *base, int fd, sldns_buffer* buffer, c->dnscrypt = 0; c->dnscrypt_buffer = NULL; #endif -#ifdef HAVE_NGTCP2 - c->doq_socket = doq_server_socket_create(table, rnd, ssl_service_key, - ssl_service_pem, c, base, cfg); + c->doq_socket = doq_server_socket_create(table, rnd, quic_sslctx, c, + base, cfg); if(!c->doq_socket) { log_err("could not create doq comm_point"); comm_point_delete(c); return NULL; } -#endif c->inuse = 0; c->callback = callback; c->cb_arg = callback_arg; @@ -5940,8 +5886,7 @@ comm_point_create_doq(struct comm_base *base, int fd, sldns_buffer* buffer, (void)socket; (void)rnd; (void)table; - (void)ssl_service_key; - (void)ssl_service_pem; + (void)quic_sslctx; (void)cfg; sock_close(fd); return NULL; diff --git a/util/netevent.h b/util/netevent.h index acc4887b1..a9deeecea 100644 --- a/util/netevent.h +++ b/util/netevent.h @@ -593,8 +593,7 @@ struct comm_point* comm_point_create_udp_ancil(struct comm_base* base, * @param socket: and opened socket properties will be passed to your callback function. * @param table: the doq connection table for the host. * @param rnd: random generator to use. - * @param ssl_service_key: the ssl service key file. - * @param ssl_service_pem: the ssl service pem file. + * @param quic_sslctx: the quic ssl context. * @param cfg: config file struct. * @return: returns the allocated communication point. NULL on error. * Sets timeout to NULL. Turns off TCP options. @@ -603,8 +602,8 @@ struct comm_point* comm_point_create_doq(struct comm_base* base, int fd, struct sldns_buffer* buffer, comm_point_callback_type* callback, void* callback_arg, struct unbound_socket* socket, struct doq_table* table, - struct ub_randstate* rnd, const char* ssl_service_key, - const char* ssl_service_pem, struct config_file* cfg); + struct ub_randstate* rnd, const void* quic_sslctx, + struct config_file* cfg); /** * Create a TCP listener comm point. Calls malloc. @@ -1045,12 +1044,6 @@ struct doq_server_socket { struct ub_randstate* rnd; /** if address validation is enabled */ uint8_t validate_addr; - /** the ssl service key file */ - char* ssl_service_key; - /** the ssl service pem file */ - char* ssl_service_pem; - /** the ssl verify pem file */ - char* ssl_verify_pem; /** the server scid length */ int sv_scidlen; /** the idle timeout in nanoseconds */ diff --git a/winrc/win_svc.c b/winrc/win_svc.c index 49d4251fa..bd28bd206 100644 --- a/winrc/win_svc.c +++ b/winrc/win_svc.c @@ -366,6 +366,11 @@ service_init(int r, struct daemon** d, struct config_file** c) if(!(daemon->listen_sslctx = listen_sslctx_create( cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) fatal_exit("could not set up listen SSL_CTX"); +#ifdef HAVE_NGTCP2 + if(!(daemon->quic_sslctx = quic_sslctx_create( + cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) + fatal_exit("could not set up quic SSL_CTX"); +#endif /* HAVE_NGTCP2 */ } if(!(daemon->connect_sslctx = connect_sslctx_create(NULL, NULL, cfg->tls_cert_bundle, cfg->tls_win_cert))) From e82a691efe869187e895b1f5ac4ae345adc754fa Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 3 Dec 2024 14:21:34 +0100 Subject: [PATCH 018/218] Changelog entry for #1187: - Merge #1187: Create the SSL_CTX for QUIC before chroot and privilege drop. --- doc/Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 5e625dc97..229983346 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -7,6 +7,8 @@ - Merge #1198: Fix log-servfail with serve expired and no useful cache contents. - Safeguard alias loop while looking in the cache for expired answers. + - Merge #1187: Create the SSL_CTX for QUIC before chroot and privilege + drop. 22 November 2024: Yorgos - Fix #1175: serve-expired does not adhere to secure-by-default From ded4c82ced83f12f0498eed40a815c11a5e6848b Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 3 Dec 2024 16:03:05 +0100 Subject: [PATCH 019/218] - Fix typo in log_servfail.tdir test. --- doc/Changelog | 1 + testdata/log_servfail.tdir/log_servfail.test | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index 229983346..534f2e973 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -9,6 +9,7 @@ - Safeguard alias loop while looking in the cache for expired answers. - Merge #1187: Create the SSL_CTX for QUIC before chroot and privilege drop. + - Fix typo in log_servfail.tdir test. 22 November 2024: Yorgos - Fix #1175: serve-expired does not adhere to secure-by-default diff --git a/testdata/log_servfail.tdir/log_servfail.test b/testdata/log_servfail.tdir/log_servfail.test index a6156ba04..1d19e5ca3 100644 --- a/testdata/log_servfail.tdir/log_servfail.test +++ b/testdata/log_servfail.tdir/log_servfail.test @@ -1,4 +1,4 @@ -# #-- cookie_file.test --# +# #-- log_servfail.test --# # source the master var file when it's there [ -f ../.tpkg.var.master ] && source ../.tpkg.var.master # use .tpkg.var.test for in test variable passing From eb08dc617a52e1c3e28300ac6cc2de74edfb6146 Mon Sep 17 00:00:00 2001 From: Maarten Aertsen Date: Fri, 13 Dec 2024 13:12:03 +0100 Subject: [PATCH 020/218] set persist-credentials: false per zizmor suggestion --- .github/workflows/analysis_ports.yml | 1 + .github/workflows/ci.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/analysis_ports.yml b/.github/workflows/analysis_ports.yml index 0388e3cca..85b723912 100644 --- a/.github/workflows/analysis_ports.yml +++ b/.github/workflows/analysis_ports.yml @@ -193,6 +193,7 @@ jobs: - uses: actions/checkout@v4 with: submodules: false + persist-credentials: false - name: test_windows if: ${{ matrix.test_windows == 'yes' }} shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f01ed3273..6ca3bc414 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: configure run: ./configure --enable-debug - name: make From 71d821fde9a583204a955f89ba1329954b13902f Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 13 Dec 2024 13:43:29 +0100 Subject: [PATCH 021/218] Changelog entry for #1204: - Merge #1204: ci: set persist-credentials: false for actions/checkout per zizmor suggestion. --- doc/Changelog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 534f2e973..d30035743 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +13 December 2024: Yorgos + - Merge #1204: ci: set persist-credentials: false for actions/checkout + per zizmor suggestion. + 3 December 2024: Yorgos - Merge #1189: Fix the dname_str method to cause conversion errors when the domain name length is 255. From e57e537c85ddf5c175b8e785e34cc21c85ec1168 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 20 Dec 2024 15:04:34 +0100 Subject: [PATCH 022/218] - For #1207: [FR] Support for RESINFO RRType 261 (RFC9606), add LDNS_RR_TYPE_RESINFO similar to LDNS_RR_TYPE_TXT. --- doc/Changelog | 4 ++++ sldns/rrdef.c | 6 ++++++ sldns/rrdef.h | 2 ++ 3 files changed, 12 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index d30035743..8b2468d46 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +20 December 2024: Yorgos + - For #1207: [FR] Support for RESINFO RRType 261 (RFC9606), add + LDNS_RR_TYPE_RESINFO similar to LDNS_RR_TYPE_TXT. + 13 December 2024: Yorgos - Merge #1204: ci: set persist-credentials: false for actions/checkout per zizmor suggestion. diff --git a/sldns/rrdef.c b/sldns/rrdef.c index e81ebb1fc..9b4d50f44 100644 --- a/sldns/rrdef.c +++ b/sldns/rrdef.c @@ -617,6 +617,12 @@ static sldns_rr_descriptor rdata_field_descriptors[] = { {(enum sldns_enum_rr_type)0, "TYPE258", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 }, #endif +{(enum sldns_enum_rr_type)0, "TYPE259", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 }, +{(enum sldns_enum_rr_type)0, "TYPE260", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 }, + + /* 261 */ + {LDNS_RR_TYPE_RESINFO, "RESINFO", 1, 0, NULL, LDNS_RDF_TYPE_STR, LDNS_RR_NO_COMPRESS, 0 }, + /* split in array, no longer contiguous */ #ifdef DRAFT_RRTYPES diff --git a/sldns/rrdef.h b/sldns/rrdef.h index 7cadf7beb..5c6074819 100644 --- a/sldns/rrdef.h +++ b/sldns/rrdef.h @@ -229,6 +229,8 @@ enum sldns_enum_rr_type LDNS_RR_TYPE_CAA = 257, /* RFC 6844 */ LDNS_RR_TYPE_AVC = 258, + LDNS_RR_TYPE_RESINFO = 261, /* RFC 9606 */ + /** DNSSEC Trust Authorities */ LDNS_RR_TYPE_TA = 32768, /* RFC 4431, 5074, DNSSEC Lookaside Validation */ From fff9f62a1ec008185868a369207b0d56ffb7dcb3 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 31 Dec 2024 16:28:12 +0100 Subject: [PATCH 023/218] Serve expired cache update fixes (#1174) - Fixes a regression bug with serve-expired that appeared in 1.22.0 and would not allow the iterator to update the cache with not-yet-validated entries resulting in increased outgoing traffic. - Treat serve_expired_norec_ttl as a backoff timer for failed updates of expired records. - Try to use expired answers instead of SERVFAIL if serve-expired is enabled even without serve-expired-client-timeout. - Add suggestion to refresh the cached norec_ttl and expired_ttl when a response cannot update the usable expired entry. --- daemon/worker.c | 6 +- services/cache/dns.c | 41 +++++++++- services/mesh.c | 6 +- .../serve_expired_client_timeout_servfail.rpl | 48 +++++++++--- ...serve_expired_client_timeout_val_bogus.rpl | 18 +++-- testdata/serve_expired_ttl_reset.rpl | 22 +++--- testdata/serve_expired_val_bogus.rpl | 75 +++++++++++++++++-- 7 files changed, 170 insertions(+), 46 deletions(-) diff --git a/daemon/worker.c b/daemon/worker.c index 413abdb87..3985d1108 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -1845,10 +1845,10 @@ lookup_cache: * its qname must be that used for cache * lookup. */ if((worker->env.cfg->prefetch && - *worker->env.now >= rep->prefetch_ttl) || + rep->prefetch_ttl <= *worker->env.now) || (worker->env.cfg->serve_expired && - *worker->env.now > rep->ttl)) { - + rep->ttl < *worker->env.now && + !(*worker->env.now < rep->serve_expired_norec_ttl))) { time_t leeway = rep->ttl - *worker->env.now; if(rep->ttl < *worker->env.now) leeway = 0; diff --git a/services/cache/dns.c b/services/cache/dns.c index d9536c0e7..351b3568c 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -1056,7 +1056,7 @@ dns_cache_lookup(struct module_env* env, int dns_cache_store(struct module_env* env, struct query_info* msgqinf, - struct reply_info* msgrep, int is_referral, time_t leeway, int pside, + struct reply_info* msgrep, int is_referral, time_t leeway, int pside, struct regional* region, uint32_t flags, time_t qstarttime, int is_valrec) { @@ -1066,7 +1066,7 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf, * useful expired record exists. */ struct msgreply_entry* e = msg_cache_lookup(env, msgqinf->qname, msgqinf->qname_len, msgqinf->qtype, - msgqinf->qclass, flags, 0, 0); + msgqinf->qclass, flags, 0, 1); if(e) { struct reply_info* cached = e->entry.data; if(cached->ttl < *env->now @@ -1081,7 +1081,42 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf, && cached->security != sec_status_bogus && (env->need_to_validate && msgrep->security == sec_status_unchecked) - && !is_valrec) { + /* Exceptions to that rule are: + * o recursions that don't need validation but + * need to update the cache for coherence + * (delegation information while iterating, + * DNSKEY and DS lookups from validator) + * o explicit RRSIG queries that are not + * validated. */ + && !is_valrec + && msgqinf->qtype != LDNS_RR_TYPE_RRSIG) { + if((int)FLAGS_GET_RCODE(msgrep->flags) != + LDNS_RCODE_NOERROR && + (int)FLAGS_GET_RCODE(msgrep->flags) != + LDNS_RCODE_NXDOMAIN) { + /* The current response has an + * erroneous rcode. Adjust norec time + * so that additional lookups are not + * performed for some time. */ + verbose(VERB_ALGO, "set " + "serve-expired-norec-ttl for " + "response in cache"); + cached->serve_expired_norec_ttl = + NORR_TTL + *env->now; + if(env->cfg->serve_expired_ttl_reset && + cached->serve_expired_ttl + < *env->now + + env->cfg->serve_expired_ttl) { + /* Reset serve-expired-ttl for + * valid response in cache. */ + verbose(VERB_ALGO, "reset " + "serve-expired-ttl " + "for response in cache"); + cached->serve_expired_ttl = + *env->now + + env->cfg->serve_expired_ttl; + } + } verbose(VERB_ALGO, "a validated expired entry " "could be overwritten, skip caching " "the new message at this stage"); diff --git a/services/mesh.c b/services/mesh.c index a25094d12..2289277ea 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -1512,8 +1512,10 @@ void mesh_query_done(struct mesh_state* mstate) } if(mstate->s.return_rcode == LDNS_RCODE_SERVFAIL || (rep && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_SERVFAIL)) { - /* we are SERVFAILing; check for expired answer here */ - mesh_serve_expired_callback(mstate); + if(mstate->s.env->cfg->serve_expired) { + /* we are SERVFAILing; check for expired answer here */ + mesh_respond_serve_expired(mstate); + } if((mstate->reply_list || mstate->cb_list) && mstate->s.env->cfg->log_servfail && !mstate->s.env->cfg->val_log_squelch) { diff --git a/testdata/serve_expired_client_timeout_servfail.rpl b/testdata/serve_expired_client_timeout_servfail.rpl index 13d03da6c..3c5b35e17 100644 --- a/testdata/serve_expired_client_timeout_servfail.rpl +++ b/testdata/serve_expired_client_timeout_servfail.rpl @@ -9,7 +9,6 @@ server: ede: yes ede-serve-expired: yes - stub-zone: name: "example.com" stub-addr: 1.2.3.4 @@ -25,8 +24,10 @@ SCENARIO_BEGIN Test serve-expired with client-timeout and a SERVFAIL upstream re ; - check that we get the expired cached answer ; - query again (the answer is available on the upstream server now) ; - check that we get the immediate expired answer back instead -; - (the upstream query does happen after the expired reply and updates the cache) -; - query again (the upstream has no answer) +; - query again (the answer is available on the upstream server now) +; - check that we *still* get the immediate expired answer back instead, recursion is blocked for NORR_TTL(5) +; - wait for NORR_TTL(5) to expire +; - query again ; - check that we get the freshly cached answer ; ns.example.com. @@ -73,7 +74,7 @@ RANGE_BEGIN 30 40 RANGE_END ; ns.example.com. -RANGE_BEGIN 50 60 +RANGE_BEGIN 50 100 ADDRESS 1.2.3.4 ; response to A query ENTRY_BEGIN @@ -148,12 +149,8 @@ ENTRY_BEGIN example.com. IN A ENTRY_END -; Allow for upstream query to resolve. -STEP 51 TRAFFIC - ; Check that we got an immediate stale answer because of the previous failure, -; regardless if upstream has the answer already in this range. The query will -; be resolved after the immediate cached answer and will cache the result. +; regardless if upstream has the answer already in this range. STEP 60 CHECK_ANSWER ENTRY_BEGIN MATCH all ttl ede=3 @@ -170,15 +167,42 @@ ENTRY_END ; Query again STEP 70 QUERY +ENTRY_BEGIN + REPLY RD DO + SECTION QUESTION + example.com. IN A +ENTRY_END + +; Check that we still get the immediate stale answer because of the previous failure, +; regardless if upstream has the answer already in this range. NORR_TTL(5) blocks us from +; recursion. +STEP 80 CHECK_ANSWER +ENTRY_BEGIN + MATCH all ttl ede=3 + REPLY QR RD RA DO NOERROR + SECTION QUESTION + example.com. IN A + SECTION ANSWER + example.com. 123 IN A 5.6.7.8 + SECTION AUTHORITY + example.com. 123 IN NS ns.example.com. + SECTION ADDITIONAL + ns.example.com. 123 IN A 1.2.3.4 +ENTRY_END + +; Let NORR_TTL(5) expire +STEP 81 TIME_PASSES ELAPSE 5 + +; Query again +STEP 90 QUERY ENTRY_BEGIN REPLY RD SECTION QUESTION example.com. IN A ENTRY_END -; Check that we got the cached updated answer from the previous step since -; there is no upstream in this range. -STEP 80 CHECK_ANSWER +; Check fresh reply +STEP 100 CHECK_ANSWER ENTRY_BEGIN MATCH all ttl REPLY QR RD RA NOERROR diff --git a/testdata/serve_expired_client_timeout_val_bogus.rpl b/testdata/serve_expired_client_timeout_val_bogus.rpl index 4334168cd..47a6545a4 100644 --- a/testdata/serve_expired_client_timeout_val_bogus.rpl +++ b/testdata/serve_expired_client_timeout_val_bogus.rpl @@ -30,13 +30,13 @@ SCENARIO_BEGIN Test serve-expired with client-timeout and bogus answer ; - wait for the record to expire ; - (upstream now has a bogus response) ; - query again for www.example.com. IN A -; - check that we get the expired valid response instead -; - query once more +; - check that we get the expired valid response instead; recursion is blocked for NORR_TTL(5) because of the failure ; - (upstream has the valid response again) +; - query once more ; - check that we get the immediate expired valid response -; - (the prefetch query updates the cache with the valid response) +; - let NORR_TTL(5) expire ; - query one last time -; - check that we get the immediate valid cache response; upstream does not have an answer at this moment +; - check that we get the immediate valid cache response ; The example.com NS and ns.example.com A record are commented out. ; This to make the test succeed. It then keeps the dnssec valid lookup. @@ -199,7 +199,7 @@ RANGE_END ;; ;; ns.example.com. with valid data again ;; -RANGE_BEGIN 40 60 +RANGE_BEGIN 40 70 ADDRESS 1.2.3.4 ; response to query of interest ENTRY_BEGIN @@ -279,7 +279,7 @@ SECTION QUESTION www.example.com. IN A ENTRY_END -; immediate cached answer because upstream is valid again +; immediate cached answer; although upstream is valid again STEP 50 CHECK_ANSWER ENTRY_BEGIN MATCH all ttl ede=3 @@ -297,7 +297,9 @@ SECTION ADDITIONAL ;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} ENTRY_END -; upstream query is resolved before this query comes in +STEP 51 TIME_PASSES ELAPSE 5 + +; query one last time STEP 60 QUERY ENTRY_BEGIN REPLY RD DO @@ -305,7 +307,7 @@ SECTION QUESTION www.example.com. IN A ENTRY_END -; prefetch query updated the cache, since there is no upstream response in this range +; this is the fresh valid response STEP 70 CHECK_ANSWER ENTRY_BEGIN MATCH all ttl diff --git a/testdata/serve_expired_ttl_reset.rpl b/testdata/serve_expired_ttl_reset.rpl index faedb1cfc..9f215c96b 100644 --- a/testdata/serve_expired_ttl_reset.rpl +++ b/testdata/serve_expired_ttl_reset.rpl @@ -16,11 +16,11 @@ SCENARIO_BEGIN Serve expired ttl with reset on forwarder with a timeout on upstr ; - Wait for it to expire (+ serve-expired-ttl) ; - Send query again ; - Upstream timeouts -; - Error response from iterator SERVFAIL, resets expired-ttl on cache -; - Check we are getting the SERVFAIL response +; - Error response from iterator SERVFAIL, resets expired-ttl on cache and sets norec_ttl blocking recursion +; - Check we are getting the cached response because it was expired-ttl-reset ; - Query again -; - Check we are getting the expired answer -; - Upstream still timeouts +; - Check we are getting the expired answer; prefetching is blocked by norec_ttl +; - If there was prefetching the test would fail with the pending upstream query STEP 1 QUERY ENTRY_BEGIN @@ -54,7 +54,7 @@ STEP 4 TIME_PASSES ELAPSE 12 STEP 5 QUERY ENTRY_BEGIN -REPLY RD +REPLY RD DO SECTION QUESTION www.example.com. IN A ENTRY_END @@ -67,14 +67,16 @@ STEP 8 TIMEOUT STEP 9 TIMEOUT STEP 10 TIMEOUT -; Returns servfail +; Returns ; but error response from iterator resets the expired ttl STEP 11 CHECK_ANSWER ENTRY_BEGIN -MATCH all ttl -REPLY QR RA RD SERVFAIL +MATCH all ttl ede=3 +REPLY QR RA RD DO NOERROR SECTION QUESTION www.example.com. IN A +SECTION ANSWER +www.example.com. 123 IN A 0.0.0.0 ENTRY_END ; Query again @@ -96,8 +98,4 @@ SECTION ANSWER www.example.com. 123 IN A 0.0.0.0 ENTRY_END -; But the pending query times out! -; Only one because RTT reached the limit. -STEP 16 TIMEOUT - SCENARIO_END diff --git a/testdata/serve_expired_val_bogus.rpl b/testdata/serve_expired_val_bogus.rpl index 6c28aa9a6..54b66fe98 100644 --- a/testdata/serve_expired_val_bogus.rpl +++ b/testdata/serve_expired_val_bogus.rpl @@ -31,12 +31,18 @@ SCENARIO_BEGIN Test serve-expired with client-timeout and bogus answer ; - (upstream now has a bogus response) ; - query again for www.example.com. IN A ; - check that we get the immediate expired valid response -; - (prefetch response is bogus and is not cached) +; - (prefetch response is bogus and is not cached; recursion is blocked for NORR_TTL(5) because of the failure) +; - (upstream has a valid response again) ; - query once more -; - check that we still get the immediate expired valid response and not the fresh bogus one -; - (upstream has a valid response again; prefetch will update the cache) +; - check that we still get the immediate expired valid response (prefetch will not trigger because of NORR_TTL(5)) +; - query and check that cache was not updated +; - let NORR_TTL(5) expire +; - query once more +; - check that we still get the immediate expired valid response +; - (prefetch should be allowed to refresh the record at this point) +; - (upstream does not have the answer anymore) ; - query one last time -; - check that we get an immediate valid cache response +; - check that we get the immediate valid cache response ; The example.com NS and ns.example.com A record are commented out. ; This to make the test succeed. It then keeps the dnssec valid lookup. @@ -273,6 +279,7 @@ SECTION ADDITIONAL ;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} ENTRY_END +; query with response available on the server STEP 40 QUERY ENTRY_BEGIN REPLY RD DO @@ -280,7 +287,8 @@ SECTION QUESTION www.example.com. IN A ENTRY_END -; this is still the immediate cache response because the previous upstream response was bogus +; this is still the immediate expired cache response because the previous upstream response was bogus +; upstream query did not go out because of the previous failure NORR_TTL(5). STEP 50 CHECK_ANSWER ENTRY_BEGIN MATCH all ttl ede=3 @@ -298,6 +306,7 @@ SECTION ADDITIONAL ;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} ENTRY_END +; query with response available STEP 60 QUERY ENTRY_BEGIN REPLY RD DO @@ -305,9 +314,63 @@ SECTION QUESTION www.example.com. IN A ENTRY_END -; this is the immediate cache response because the previous upstream response was valid +; this is still the immediate expired cache response because resolution is blocked for NORR_TTL(5) STEP 70 CHECK_ANSWER ENTRY_BEGIN +MATCH all ttl ede=3 +REPLY QR RD RA AD DO NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 123 IN A 10.20.30.40 +www.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} +SECTION AUTHORITY +;example.com. 123 IN NS ns.example.com. +;example.com. 123 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +;ns.example.com. 123 IN A 1.2.3.4 +;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} +ENTRY_END + +; expire NORR_TTL(5) +STEP 71 TIME_PASSES ELAPSE 5 + +; query again +STEP 80 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; this is still the immediate expired cache response but prefetching will be allowed to update the cache +STEP 90 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl ede=3 +REPLY QR RD RA AD DO NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. 123 IN A 10.20.30.40 +www.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854} +SECTION AUTHORITY +;example.com. 123 IN NS ns.example.com. +;example.com. 123 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +;ns.example.com. 123 IN A 1.2.3.4 +;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854} +ENTRY_END + +STEP 100 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; this is the immediate cache response because the previous upstream response was valid +STEP 110 CHECK_ANSWER +ENTRY_BEGIN MATCH all ttl REPLY QR RD RA AD DO NOERROR SECTION QUESTION From eb36c880de40a09e5a1dab8029ec401ea2904929 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 31 Dec 2024 16:30:35 +0100 Subject: [PATCH 024/218] Changelog entry for #1174: - Merge #1174: Serve expired cache update fixes. Fixes a regression bug with serve-expired that appeared in 1.22.0 and would not allow the iterator to update the cache with not-yet-validated entries resulting in increased outgoing traffic. --- doc/Changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 8b2468d46..514459ca1 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,9 @@ +31 December 2024: Yorgos + - Merge #1174: Serve expired cache update fixes. Fixes a regression bug + with serve-expired that appeared in 1.22.0 and would not allow the + iterator to update the cache with not-yet-validated entries resulting + in increased outgoing traffic. + 20 December 2024: Yorgos - For #1207: [FR] Support for RESINFO RRType 261 (RFC9606), add LDNS_RR_TYPE_RESINFO similar to LDNS_RR_TYPE_TXT. From 7559d26c93e63ca2f5accdc9ee9715981f934ba7 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 10 Jan 2025 12:11:59 +0100 Subject: [PATCH 025/218] - Use TCP_NODELAY on TLS sockets to speed up the TLS handshake. --- services/listen_dnsport.c | 33 +++++++++++---------------------- services/outside_network.c | 27 ++++++++++++++++++++++----- services/outside_network.h | 6 ++++-- testcode/fake_event.c | 3 ++- util/config_file.c | 20 ++++++++++++++++++++ util/config_file.h | 4 ++++ 6 files changed, 63 insertions(+), 30 deletions(-) diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index bdd14b04a..8a5cf2a18 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -703,7 +703,10 @@ create_tcp_accept_sock(struct addrinfo *addr, int v6only, int* noproto, { int s = -1; char* err; -#if defined(SO_REUSEADDR) || defined(SO_REUSEPORT) || defined(IPV6_V6ONLY) || defined(IP_TRANSPARENT) || defined(IP_BINDANY) || defined(IP_FREEBIND) || defined(SO_BINDANY) +#if defined(SO_REUSEADDR) || defined(SO_REUSEPORT) \ + || defined(IPV6_V6ONLY) || defined(IP_TRANSPARENT) \ + || defined(IP_BINDANY) || defined(IP_FREEBIND) \ + || defined(SO_BINDANY) || defined(TCP_NODELAY) int on = 1; #endif #ifdef HAVE_SYSTEMD @@ -1237,26 +1240,6 @@ set_recvpktinfo(int s, int family) return 1; } -/** see if interface is ssl, its port number == the ssl port number */ -static int -if_is_ssl(const char* ifname, const char* port, int ssl_port, - struct config_strlist* tls_additional_port) -{ - struct config_strlist* s; - char* p = strchr(ifname, '@'); - if(!p && atoi(port) == ssl_port) - return 1; - if(p && atoi(p+1) == ssl_port) - return 1; - for(s = tls_additional_port; s; s = s->next) { - if(p && atoi(p+1) == atoi(s->str)) - return 1; - if(!p && atoi(port) == atoi(s->str)) - return 1; - } - return 0; -} - /** * Helper for ports_open. Creates one interface (or NULL for default). * @param ifname: The interface ip address. @@ -1300,10 +1283,16 @@ ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, int quic_port, int http_notls_downstream, int sock_queue_timeout) { int s, noip6=0; + int is_ssl = if_is_ssl(ifname, port, ssl_port, tls_additional_port); int is_https = if_is_https(ifname, port, https_port); int is_dnscrypt = if_is_dnscrypt(ifname, port, dnscrypt_port); int is_pp2 = if_is_pp2(ifname, port, proxy_protocol_port); - int nodelay = is_https && http2_nodelay; + /* Always set TCP_NODELAY on TLS connection as it speeds up the TLS + * handshake. DoH had already such option so we respect it. + * Otherwise the server waits before sending more handshake data for + * the client ACK (Nagle's algorithm), which is delayed because the + * client waits for more data before ACKing (delayed ACK). */ + int nodelay = is_https?http2_nodelay:is_ssl; struct unbound_socket* ub_sock; int is_doq = if_is_quic(ifname, port, quic_port); const char* add = NULL; diff --git a/services/outside_network.c b/services/outside_network.c index b9475a368..0d7ec8905 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -262,12 +262,14 @@ pick_outgoing_tcp(struct pending_tcp* pend, struct waiting_tcp* w, int s) /** get TCP file descriptor for address, returns -1 on failure, * tcp_mss is 0 or maxseg size to set for TCP packets. */ int -outnet_get_tcp_fd(struct sockaddr_storage* addr, socklen_t addrlen, int tcp_mss, int dscp) +outnet_get_tcp_fd(struct sockaddr_storage* addr, socklen_t addrlen, + int tcp_mss, int dscp, int nodelay) { int s; int af; char* err; -#if defined(SO_REUSEADDR) || defined(IP_BIND_ADDRESS_NO_PORT) +#if defined(SO_REUSEADDR) || defined(IP_BIND_ADDRESS_NO_PORT) \ + || defined(TCP_NODELAY) int on = 1; #endif #ifdef INET6 @@ -320,6 +322,18 @@ outnet_get_tcp_fd(struct sockaddr_storage* addr, socklen_t addrlen, int tcp_mss, " setsockopt(.. IP_BIND_ADDRESS_NO_PORT ..) failed"); } #endif /* IP_BIND_ADDRESS_NO_PORT */ + if(nodelay) { +#if defined(IPPROTO_TCP) && defined(TCP_NODELAY) + if(setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void*)&on, + (socklen_t)sizeof(on)) < 0) { + verbose(VERB_ALGO, "outgoing tcp:" + " setsockopt(.. TCP_NODELAY ..) failed"); + } +#else + verbose(VERB_ALGO, "outgoing tcp:" + " setsockopt(.. TCP_NODELAY ..) unsupported"); +#endif /* defined(IPPROTO_TCP) && defined(TCP_NODELAY) */ + } return s; } @@ -649,7 +663,8 @@ outnet_tcp_take_into_use(struct waiting_tcp* w) } /* open socket */ - s = outnet_get_tcp_fd(&w->addr, w->addrlen, w->outnet->tcp_mss, w->outnet->ip_dscp); + s = outnet_get_tcp_fd(&w->addr, w->addrlen, w->outnet->tcp_mss, + w->outnet->ip_dscp, w->ssl_upstream); if(s == -1) return 0; @@ -3718,7 +3733,8 @@ outnet_comm_point_for_tcp(struct outside_network* outnet, sldns_buffer* query, int timeout, int ssl, char* host) { struct comm_point* cp; - int fd = outnet_get_tcp_fd(to_addr, to_addrlen, outnet->tcp_mss, outnet->ip_dscp); + int fd = outnet_get_tcp_fd(to_addr, to_addrlen, outnet->tcp_mss, + outnet->ip_dscp, ssl); if(fd == -1) { return 0; } @@ -3793,7 +3809,8 @@ outnet_comm_point_for_http(struct outside_network* outnet, { /* cp calls cb with err=NETEVENT_DONE when transfer is done */ struct comm_point* cp; - int fd = outnet_get_tcp_fd(to_addr, to_addrlen, outnet->tcp_mss, outnet->ip_dscp); + int fd = outnet_get_tcp_fd(to_addr, to_addrlen, outnet->tcp_mss, + outnet->ip_dscp, ssl); if(fd == -1) { return 0; } diff --git a/services/outside_network.h b/services/outside_network.h index 467c81f60..0a77e3388 100644 --- a/services/outside_network.h +++ b/services/outside_network.h @@ -743,9 +743,11 @@ void reuse_write_wait_remove(struct reuse_tcp* reuse, struct waiting_tcp* w); void reuse_write_wait_push_back(struct reuse_tcp* reuse, struct waiting_tcp* w); /** get TCP file descriptor for address, returns -1 on failure, - * tcp_mss is 0 or maxseg size to set for TCP packets. */ + * tcp_mss is 0 or maxseg size to set for TCP packets, + * nodelay (TCP_NODELAY) should be set for TLS connections to speed up the TLS + * handshake.*/ int outnet_get_tcp_fd(struct sockaddr_storage* addr, socklen_t addrlen, - int tcp_mss, int dscp); + int tcp_mss, int dscp, int nodelay); /** * Create udp commpoint suitable for sending packets to the destination. diff --git a/testcode/fake_event.c b/testcode/fake_event.c index be61ed5c6..840a687d9 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -1938,7 +1938,8 @@ int comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet, } int outnet_get_tcp_fd(struct sockaddr_storage* ATTR_UNUSED(addr), - socklen_t ATTR_UNUSED(addrlen), int ATTR_UNUSED(tcp_mss), int ATTR_UNUSED(dscp)) + socklen_t ATTR_UNUSED(addrlen), int ATTR_UNUSED(tcp_mss), + int ATTR_UNUSED(dscp), int ATTR_UNUSED(nodelay)) { log_assert(0); return -1; diff --git a/util/config_file.c b/util/config_file.c index 58567dccc..dbe1b7081 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -2796,6 +2796,26 @@ int cfg_has_https(struct config_file* cfg) return 0; } +/** see if interface is ssl, its port number == the ssl port number */ +int +if_is_ssl(const char* ifname, const char* port, int ssl_port, + struct config_strlist* tls_additional_port) +{ + struct config_strlist* s; + char* p = strchr(ifname, '@'); + if(!p && atoi(port) == ssl_port) + return 1; + if(p && atoi(p+1) == ssl_port) + return 1; + for(s = tls_additional_port; s; s = s->next) { + if(p && atoi(p+1) == atoi(s->str)) + return 1; + if(!p && atoi(port) == atoi(s->str)) + return 1; + } + return 0; +} + /** see if interface is PROXYv2, its port number == the proxy port number */ int if_is_pp2(const char* ifname, const char* port, diff --git a/util/config_file.h b/util/config_file.h index 2969f8433..07e539f06 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -1405,6 +1405,10 @@ int if_is_https(const char* ifname, const char* port, int https_port); */ int cfg_has_https(struct config_file* cfg); +/** see if interface is ssl, its port number == the ssl port number */ +int if_is_ssl(const char* ifname, const char* port, int ssl_port, + struct config_strlist* tls_additional_port); + /** see if interface is PROXYv2, its port number == the proxy port number */ int if_is_pp2(const char* ifname, const char* port, struct config_strlist* proxy_protocol_port); From 716f3df38562fd826f4984b844d35f99aac45f2b Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 10 Jan 2025 13:54:49 +0100 Subject: [PATCH 026/218] Changelog entry for #1214: - Merge #1214: Use TCP_NODELAY on TLS sockets to speed up the TLS handshake. --- doc/Changelog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 514459ca1..7842da956 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +10 January 2025: Yorgos + - Merge #1214: Use TCP_NODELAY on TLS sockets to speed up the TLS + handshake. + 31 December 2024: Yorgos - Merge #1174: Serve expired cache update fixes. Fixes a regression bug with serve-expired that appeared in 1.22.0 and would not allow the From 62a0e038016122acab06e4795ec35172479247eb Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 13 Jan 2025 11:33:24 +0100 Subject: [PATCH 027/218] - Fix #1213: Misleading error message on default access control causing refuse. --- daemon/acl_list.c | 8 ++++++-- daemon/acl_list.h | 2 ++ doc/Changelog | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/daemon/acl_list.c b/daemon/acl_list.c index 605851e4f..bce55ebcd 100644 --- a/daemon/acl_list.c +++ b/daemon/acl_list.c @@ -221,7 +221,9 @@ acl_interface_insert(struct acl_list* acl_interface, struct sockaddr_storage* addr, socklen_t addrlen, enum acl_access control) { - return acl_find_or_create(acl_interface, addr, addrlen, control); + struct acl_addr* node = acl_find_or_create(acl_interface, addr, addrlen, control); + node->is_inteface = 1; + return node; } /** apply acl_tag string */ @@ -805,7 +807,9 @@ log_acl_action(const char* action, struct sockaddr_storage* addr, addr_to_str(&acladdr->node.addr, acladdr->node.addrlen, n, sizeof(n)); verbose(VERB_ALGO, "%s query from %s port %d because of " - "%s/%d %s", action, a, (int)port, n, acladdr->node.net, + "%s/%d %s%s", action, a, (int)port, n, + acladdr->node.net, + acladdr->is_inteface?"(ACL on interface IP) ":"", acl_access_to_str(acl)); } else { verbose(VERB_ALGO, "%s query from %s port %d", action, a, diff --git a/daemon/acl_list.h b/daemon/acl_list.h index ee679eb6e..5c34280b6 100644 --- a/daemon/acl_list.h +++ b/daemon/acl_list.h @@ -107,6 +107,8 @@ struct acl_addr { struct config_strlist** tag_datas; /** size of the tag_datas array */ size_t tag_datas_size; + /* If the acl node is for an interface */ + int is_inteface; /* view element, NULL if none */ struct view* view; }; diff --git a/doc/Changelog b/doc/Changelog index 7842da956..f55fb05ed 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +13 January 2025: Yorgos + - Fix #1213: Misleading error message on default access control causing + refuse. + 10 January 2025: Yorgos - Merge #1214: Use TCP_NODELAY on TLS sockets to speed up the TLS handshake. From c3b5bff31183b204023bb9dbb74e4e9ab9ed256b Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 13 Jan 2025 12:32:16 +0100 Subject: [PATCH 028/218] - Fix typo. --- daemon/acl_list.c | 4 ++-- daemon/acl_list.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/acl_list.c b/daemon/acl_list.c index bce55ebcd..362de0a2c 100644 --- a/daemon/acl_list.c +++ b/daemon/acl_list.c @@ -222,7 +222,7 @@ acl_interface_insert(struct acl_list* acl_interface, enum acl_access control) { struct acl_addr* node = acl_find_or_create(acl_interface, addr, addrlen, control); - node->is_inteface = 1; + node->is_interface = 1; return node; } @@ -809,7 +809,7 @@ log_acl_action(const char* action, struct sockaddr_storage* addr, verbose(VERB_ALGO, "%s query from %s port %d because of " "%s/%d %s%s", action, a, (int)port, n, acladdr->node.net, - acladdr->is_inteface?"(ACL on interface IP) ":"", + acladdr->is_interface?"(ACL on interface IP) ":"", acl_access_to_str(acl)); } else { verbose(VERB_ALGO, "%s query from %s port %d", action, a, diff --git a/daemon/acl_list.h b/daemon/acl_list.h index 5c34280b6..6e6e5e2e9 100644 --- a/daemon/acl_list.h +++ b/daemon/acl_list.h @@ -108,7 +108,7 @@ struct acl_addr { /** size of the tag_datas array */ size_t tag_datas_size; /* If the acl node is for an interface */ - int is_inteface; + int is_interface; /* view element, NULL if none */ struct view* view; }; From f4881bd81a71b3a08814fc2ff2a7da3153e0c5f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= Date: Tue, 14 Jan 2025 13:55:10 +0100 Subject: [PATCH 029/218] Add unbound members group access to control key Recent openssl genrsa does not use umask for generated keys. There is no strong reason why every member of unbound group should be able read server key. But control key would be quite useful to be group readable and to allow control access to whole group. Allowing access to control by group membership, not via sudo. --- smallapp/unbound-control-setup.sh.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/smallapp/unbound-control-setup.sh.in b/smallapp/unbound-control-setup.sh.in index 4a358f6bd..c2a79a242 100644 --- a/smallapp/unbound-control-setup.sh.in +++ b/smallapp/unbound-control-setup.sh.in @@ -204,7 +204,8 @@ fi # remove unused permissions chmod o-rw \ "$SVR_BASE.pem" \ - "$SVR_BASE.key" \ + "$SVR_BASE.key" +chmod g+r,o-rw \ "$CTL_BASE.pem" \ "$CTL_BASE.key" From b2fec3be1193d478cf91b0787012354bbd4fda34 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 14 Jan 2025 16:38:53 +0100 Subject: [PATCH 030/218] - Take configured auth zones into consideration when checking if a request needs to be forwarded. --- iterator/iter_fwd.c | 34 ++++++++ iterator/iter_utils.c | 10 ++- testdata/iter_fwdstubauth.rpl | 155 ++++++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 testdata/iter_fwdstubauth.rpl diff --git a/iterator/iter_fwd.c b/iterator/iter_fwd.c index 709911656..048d67401 100644 --- a/iterator/iter_fwd.c +++ b/iterator/iter_fwd.c @@ -331,6 +331,30 @@ make_stub_holes(struct iter_forwards* fwd, struct config_file* cfg) return 1; } +/** make NULL entries for auths */ +static int +make_auth_holes(struct iter_forwards* fwd, struct config_file* cfg) +{ + struct config_auth* a; + uint8_t* dname; + size_t dname_len; + for(a = cfg->auths; a; a = a->next) { + if(!a->name) continue; + dname = sldns_str2wire_dname(a->name, &dname_len); + if(!dname) { + log_err("cannot parse auth name '%s'", a->name); + return 0; + } + if(!fwd_add_stub_hole(fwd, LDNS_RR_CLASS_IN, dname)) { + free(dname); + log_err("out of memory"); + return 0; + } + free(dname); + } + return 1; +} + int forwards_apply_cfg(struct iter_forwards* fwd, struct config_file* cfg) { @@ -353,6 +377,16 @@ forwards_apply_cfg(struct iter_forwards* fwd, struct config_file* cfg) lock_rw_unlock(&fwd->lock); return 0; } + /* TODO: Now we punch holes for auth zones as well so that in + * iterator:forward_request() we see the configured + * delegation point, but code flow/naming is hard to follow. + * Consider having a single tree with configured + * delegation points for all categories + * (stubs, forwards, auths). */ + if(!make_auth_holes(fwd, cfg)) { + lock_rw_unlock(&fwd->lock); + return 0; + } fwd_init_parents(fwd); lock_rw_unlock(&fwd->lock); return 1; diff --git a/iterator/iter_utils.c b/iterator/iter_utils.c index e5cf13eca..5acd7a156 100644 --- a/iterator/iter_utils.c +++ b/iterator/iter_utils.c @@ -1489,14 +1489,15 @@ iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf, /* check stub */ if (stub != NULL && stub->dp != NULL) { + enum verbosity_value level = VERB_ALGO; int stub_no_cache = stub->dp->no_cache; lock_rw_unlock(&qstate->env->fwds->lock); - if(stub_no_cache) { + if(verbosity >= level && stub_no_cache) { char qname[LDNS_MAX_DOMAINLEN]; char dpname[LDNS_MAX_DOMAINLEN]; dname_str(qinf->qname, qname); dname_str(stub->dp->name, dpname); - verbose(VERB_ALGO, "stub for %s %s has no_cache", qname, dpname); + verbose(level, "stub for %s %s has no_cache", qname, dpname); } if(retdpname) { if(stub->dp->namelen > dpname_storage_len) { @@ -1517,14 +1518,15 @@ iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf, /* Check for forward. */ if (dp) { + enum verbosity_value level = VERB_ALGO; int dp_no_cache = dp->no_cache; lock_rw_unlock(&qstate->env->hints->lock); - if(dp_no_cache) { + if(verbosity >= level && dp_no_cache) { char qname[LDNS_MAX_DOMAINLEN]; char dpname[LDNS_MAX_DOMAINLEN]; dname_str(qinf->qname, qname); dname_str(dp->name, dpname); - verbose(VERB_ALGO, "forward for %s %s has no_cache", qname, dpname); + verbose(level, "forward for %s %s has no_cache", qname, dpname); } if(retdpname) { if(dp->namelen > dpname_storage_len) { diff --git a/testdata/iter_fwdstubauth.rpl b/testdata/iter_fwdstubauth.rpl new file mode 100644 index 000000000..fefb6369c --- /dev/null +++ b/testdata/iter_fwdstubauth.rpl @@ -0,0 +1,155 @@ +; config options +server: + target-fetch-policy: "0 0 0 0 0" + +auth-zone: + name: "example.tld." + for-upstream: yes + for-downstream: no + fallback-enabled: no + ## this line generates zonefile: "/tmp/xxx.example.tld" + zonefile: +TEMPFILE_NAME example.tld + ## this is the inline file /tmp/xxx.example.tld + ## the tempfiles are deleted when the testrun is over. +TEMPFILE_CONTENTS example.tld +$ORIGIN tld. +example 3600 IN SOA a b 1 2 3 4 5 + 3600 IN NS ns.example.tld. +$ORIGIN example.tld. +ns 3600 IN A 1.2.3.4 +www 3600 IN A 3.3.3.3 +more 3600 IN NS ns.more.tld. +TEMPFILE_END + +forward-zone: + name: "." + forward-addr: 9.9.9.9 + +stub-zone: + name: "tld" + stub-addr: 2.3.4.5 +stub-zone: + name: "more.example.tld" + stub-addr: 2.3.4.7 +CONFIG_END + +SCENARIO_BEGIN Test iterator's ability to route the request to the correct, configured delegation point +; Preference should be auth-zone > stub-zone > forward-zone +; But configuration-wise, since everything is an entry on the forwards tree +; (or a hole in the case of stub/auth), forwards cannot be replaced by +; stubs/auth. +; Also stub/auth zones end the part of the tree that gets forwarded, e.g., +; delegations from an auth/stub cannot be caught by a higher forwarder, it will +; be recursively resolved instead. + +; '.' forwarder +RANGE_BEGIN 0 100 + ADDRESS 9.9.9.9 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.deleg.tld. IN A +SECTION ANSWER +www.deleg.tld. IN A 3.3.3.3 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.more.example.tld. IN A +SECTION ANSWER +www.more.example.tld. IN A 3.3.3.3 +ENTRY_END +RANGE_END + +; 'tld.' stub server +RANGE_BEGIN 0 100 + ADDRESS 2.3.4.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.tld. IN A +SECTION ANSWER +www.tld. IN A 3.3.3.3 +ENTRY_END +RANGE_END + +; 'more.example.tld.' stub server +RANGE_BEGIN 0 100 + ADDRESS 2.3.4.7 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.more.example.tld. IN A +SECTION ANSWER +www.more.example.tld. IN A 3.3.3.3 +ENTRY_END +RANGE_END + +; query www.tld ... +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.tld. IN A +ENTRY_END + +; ... answer should come from 'tld.' stub zone +STEP 2 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +www.tld. IN A +SECTION ANSWER +www.tld. IN A 3.3.3.3 +ENTRY_END + +; query www.example.tld ... +STEP 3 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.tld. IN A +ENTRY_END + +; ... answer should come from 'example.tld.' auth zone +STEP 4 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +www.example.tld. IN A +SECTION ANSWER +www.example.tld. IN A 3.3.3.3 +ENTRY_END + +; query www.more.example.tld ... +STEP 5 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.more.example.tld. IN A +ENTRY_END + +; ... answer should come from 'more.example.tld.' stub zone +STEP 6 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +www.more.example.tld. IN A +SECTION ANSWER +www.more.example.tld. IN A 3.3.3.3 +ENTRY_END + +SCENARIO_END From f52b2a6ea251749bb7c85e2074a6c17e28d2ae81 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 14 Jan 2025 17:18:32 +0100 Subject: [PATCH 031/218] - Add resolver.arpa and service.arpa to the default locally served zones. --- doc/Changelog | 4 ++++ doc/example.conf.in | 2 ++ doc/unbound.conf.5.in | 19 +++++++++++++++++++ services/localzone.c | 10 ++++++++++ 4 files changed, 35 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index f55fb05ed..7ffc59e0c 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +14 January 2025: Yorgos + - Add resolver.arpa and service.arpa to the default locally served + zones. + 13 January 2025: Yorgos - Fix #1213: Misleading error message on default access control causing refuse. diff --git a/doc/example.conf.in b/doc/example.conf.in index 33e12f180..de73d0044 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -811,6 +811,8 @@ server: # local-zone: "127.in-addr.arpa." nodefault # local-zone: "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa." nodefault # local-zone: "home.arpa." nodefault + # local-zone: "resolver.arpa." nodefault + # local-zone: "service.arpa." nodefault # local-zone: "onion." nodefault # local-zone: "test." nodefault # local-zone: "invalid." nodefault diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 4d1fb13f8..b6eb4493c 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -1638,6 +1638,7 @@ given zone. Use \fInodefault\fR if you use exactly that zone, if you want to use a subzone, use \fItransparent\fR. .P The default zones are localhost, reverse 127.0.0.1 and ::1, the home.arpa, +the resolver.arpa, the service.arpa, the onion, test, invalid and the AS112 zones. The AS112 zones are reverse DNS zones for private use and reserved IP addresses for which the servers on the internet cannot provide correct answers. They are configured by @@ -1693,6 +1694,24 @@ local\-data: "home.arpa. 10800 IN SOA localhost. nobody.invalid. 1 3600 1200 604800 10800" .fi .TP 10 +\h'5'\fIresolver.arpa (RFC 8375)\fR +Default content: +.nf +local\-zone: "resolver.arpa." static +local\-data: "resolver.arpa. 10800 IN NS localhost." +local\-data: "resolver.arpa. 10800 IN + SOA localhost. nobody.invalid. 1 3600 1200 604800 10800" +.fi +.TP 10 +\h'5'\fIservice.arpa (draft-ietf-dnssd-srp-25)\fR +Default content: +.nf +local\-zone: "service.arpa." static +local\-data: "service.arpa. 10800 IN NS localhost." +local\-data: "service.arpa. 10800 IN + SOA localhost. nobody.invalid. 1 3600 1200 604800 10800" +.fi +.TP 10 \h'5'\fIonion (RFC 7686)\fR Default content: .nf diff --git a/services/localzone.c b/services/localzone.c index aa7138d6a..4ff30fd26 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -943,6 +943,16 @@ int local_zone_enter_defaults(struct local_zones* zones, struct config_file* cfg log_err("out of memory adding default zone"); return 0; } + /* resolver.arpa. zone (RFC 9462) */ + if(!add_empty_default(zones, cfg, "resolver.arpa.")) { + log_err("out of memory adding default zone"); + return 0; + } + /* service.arpa. zone (draft-ietf-dnssd-srp-25) */ + if(!add_empty_default(zones, cfg, "service.arpa.")) { + log_err("out of memory adding default zone"); + return 0; + } /* onion. zone (RFC 7686) */ if(!add_empty_default(zones, cfg, "onion.")) { log_err("out of memory adding default zone"); From 394588818f271f89f4d9f36441916a8dcc849ebb Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 15 Jan 2025 10:55:31 +0100 Subject: [PATCH 032/218] - Use correct RFC number for resolver.arpa. --- doc/unbound.conf.5.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index b6eb4493c..84c842f55 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -1694,7 +1694,7 @@ local\-data: "home.arpa. 10800 IN SOA localhost. nobody.invalid. 1 3600 1200 604800 10800" .fi .TP 10 -\h'5'\fIresolver.arpa (RFC 8375)\fR +\h'5'\fIresolver.arpa (RFC 9462)\fR Default content: .nf local\-zone: "resolver.arpa." static From 1d428f2d54450e719a95ad026ce84364e1e31835 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 17 Jan 2025 10:19:26 +0100 Subject: [PATCH 033/218] Changelog entry for #1221: - Merge #1221: Consider auth zones when checking for forwarders. --- doc/Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 7ffc59e0c..9e14e13da 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +15 January 2025: Yorgos + - Merge #1221: Consider auth zones when checking for forwarders. + 14 January 2025: Yorgos - Add resolver.arpa and service.arpa to the default locally served zones. From 8672b34fcab6228d2349d766c72899978b0c2de6 Mon Sep 17 00:00:00 2001 From: Theo Buehler Date: Sat, 18 Jan 2025 10:23:57 +0100 Subject: [PATCH 034/218] Do not use DSA API unless USE_DSA is set Even if USE_DSA is unset, unbound ends up linking against OpenSSL DSA API because these guards are missing. --- sldns/keyraw.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sldns/keyraw.c b/sldns/keyraw.c index befe1f722..90a6e8533 100644 --- a/sldns/keyraw.c +++ b/sldns/keyraw.c @@ -195,6 +195,7 @@ void sldns_key_EVP_unload_gost(void) } #endif /* USE_GOST */ +#ifdef USE_DSA /* Retrieve params as BIGNUM from raw buffer */ static int sldns_key_dsa_buf_bignum(unsigned char* key, size_t len, BIGNUM** p, @@ -370,6 +371,7 @@ EVP_PKEY *sldns_key_dsa2pkey_raw(unsigned char* key, size_t len) return evp_key; #endif } +#endif /* USE_DSA */ /* Retrieve params as BIGNUM from raw buffer, n is modulus, e is exponent */ static int From e4483bbbd1fa632362c3ba08cc79783dac2d5e30 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 20 Jan 2025 15:43:44 +0100 Subject: [PATCH 035/218] Unique DoT and DoH SSL contexts to allow for different ALPN (#1222) --- daemon/daemon.c | 7 ++- daemon/daemon.h | 10 +++- daemon/unbound.c | 94 +++++++++++++++++------------ daemon/worker.c | 7 ++- dnstap/unbound-dnstap-socket.c | 3 +- services/listen_dnsport.c | 9 ++- services/listen_dnsport.h | 6 +- testcode/fake_event.c | 3 +- util/net_help.c | 104 ++++++++++++++++++++++++++------- util/net_help.h | 15 +++-- 10 files changed, 181 insertions(+), 77 deletions(-) diff --git a/daemon/daemon.c b/daemon/daemon.c index 9e1d5d4f7..76607fe52 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -954,11 +954,12 @@ daemon_delete(struct daemon* daemon) free(daemon->env); #ifdef HAVE_SSL listen_sslctx_delete_ticket_keys(); - SSL_CTX_free((SSL_CTX*)daemon->listen_sslctx); - SSL_CTX_free((SSL_CTX*)daemon->connect_sslctx); + SSL_CTX_free((SSL_CTX*)daemon->listen_dot_sslctx); + SSL_CTX_free((SSL_CTX*)daemon->listen_doh_sslctx); + SSL_CTX_free((SSL_CTX*)daemon->connect_dot_sslctx); #endif #ifdef HAVE_NGTCP2 - SSL_CTX_free((SSL_CTX*)daemon->quic_sslctx); + SSL_CTX_free((SSL_CTX*)daemon->listen_quic_sslctx); #endif free(daemon); /* lex cleanup */ diff --git a/daemon/daemon.h b/daemon/daemon.h index f93887e3d..54ab97b2d 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -97,10 +97,14 @@ struct daemon { struct listen_port* rc_ports; /** remote control connections management (for first worker) */ struct daemon_remote* rc; - /** ssl context for listening to dnstcp over ssl, and connecting ssl */ - void* listen_sslctx, *connect_sslctx; + /** ssl context for listening to dnstcp over ssl */ + void* listen_dot_sslctx; + /** ssl context for connecting to dnstcp over ssl */ + void* connect_dot_sslctx; + /** ssl context for listening to DoH */ + void* listen_doh_sslctx; /** ssl context for listening to quic */ - void* quic_sslctx; + void* listen_quic_sslctx; /** num threads allocated */ int num; /** num threads allocated in the previous config or 0 at first */ diff --git a/daemon/unbound.c b/daemon/unbound.c index f394fbde3..feea43180 100644 --- a/daemon/unbound.c +++ b/daemon/unbound.c @@ -463,6 +463,62 @@ detach(void) #endif /* HAVE_DAEMON */ } +/* setup a listening ssl context, fatal_exit() on any failure */ +static void +setup_listen_sslctx(void** ctx, int is_dot, int is_doh, struct config_file* cfg) +{ +#ifdef HAVE_SSL + if(!(*ctx = listen_sslctx_create( + cfg->ssl_service_key, cfg->ssl_service_pem, NULL, + cfg->tls_ciphers, cfg->tls_ciphersuites, + (cfg->tls_session_ticket_keys.first && + cfg->tls_session_ticket_keys.first->str[0] != 0), + is_dot, is_doh))) { + fatal_exit("could not set up listen SSL_CTX"); + } +#else /* HAVE_SSL */ + (void)ctx;(void)is_dot;(void)is_doh;(void)cfg; +#endif /* HAVE_SSL */ +} + +/* setups the needed ssl contexts, fatal_exit() on any failure */ +static void +setup_sslctxs(struct daemon* daemon, struct config_file* cfg) +{ +#ifdef HAVE_SSL + if(!(daemon->rc = daemon_remote_create(cfg))) + fatal_exit("could not set up remote-control"); + if(cfg->ssl_service_key && cfg->ssl_service_key[0]) { + /* setup the session keys; the callback to use them will be + * attached to each sslctx separately */ + if(cfg->tls_session_ticket_keys.first && + cfg->tls_session_ticket_keys.first->str[0] != 0) { + if(!listen_sslctx_setup_ticket_keys( + cfg->tls_session_ticket_keys.first)) { + fatal_exit("could not set session ticket SSL_CTX"); + } + } + (void)setup_listen_sslctx(&daemon->listen_dot_sslctx, 1, 0, cfg); +#ifdef HAVE_NGHTTP2_NGHTTP2_H + if(cfg_has_https(cfg)) { + (void)setup_listen_sslctx(&daemon->listen_doh_sslctx, 0, 1, cfg); + } +#endif +#ifdef HAVE_NGTCP2 + if(!(daemon->listen_quic_sslctx = quic_sslctx_create( + cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) { + fatal_exit("could not set up quic SSL_CTX"); + } +#endif /* HAVE_NGTCP2 */ + } + if(!(daemon->connect_dot_sslctx = connect_sslctx_create(NULL, NULL, + cfg->tls_cert_bundle, cfg->tls_win_cert))) + fatal_exit("could not set up connect SSL_CTX"); +#else /* HAVE_SSL */ + (void)daemon;(void)cfg; +#endif /* HAVE_SSL */ +} + /** daemonize, drop user privileges and chroot if needed */ static void perform_setup(struct daemon* daemon, struct config_file* cfg, int debug_mode, @@ -489,43 +545,7 @@ perform_setup(struct daemon* daemon, struct config_file* cfg, int debug_mode, #endif /* read ssl keys while superuser and outside chroot */ -#ifdef HAVE_SSL - if(!(daemon->rc = daemon_remote_create(cfg))) - fatal_exit("could not set up remote-control"); - if(cfg->ssl_service_key && cfg->ssl_service_key[0]) { - if(!(daemon->listen_sslctx = listen_sslctx_create( - cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) { - fatal_exit("could not set up listen SSL_CTX"); - } - if(cfg->tls_ciphers && cfg->tls_ciphers[0]) { - if (!SSL_CTX_set_cipher_list(daemon->listen_sslctx, cfg->tls_ciphers)) { - fatal_exit("failed to set tls-cipher %s", cfg->tls_ciphers); - } - } -#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES - if(cfg->tls_ciphersuites && cfg->tls_ciphersuites[0]) { - if (!SSL_CTX_set_ciphersuites(daemon->listen_sslctx, cfg->tls_ciphersuites)) { - fatal_exit("failed to set tls-ciphersuites %s", cfg->tls_ciphersuites); - } - } -#endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */ - if(cfg->tls_session_ticket_keys.first && - cfg->tls_session_ticket_keys.first->str[0] != 0) { - if(!listen_sslctx_setup_ticket_keys(daemon->listen_sslctx, cfg->tls_session_ticket_keys.first)) { - fatal_exit("could not set session ticket SSL_CTX"); - } - } -#ifdef HAVE_NGTCP2 - if(!(daemon->quic_sslctx = quic_sslctx_create( - cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) { - fatal_exit("could not set up quic SSL_CTX"); - } -#endif /* HAVE_NGTCP2 */ - } - if(!(daemon->connect_sslctx = connect_sslctx_create(NULL, NULL, - cfg->tls_cert_bundle, cfg->tls_win_cert))) - fatal_exit("could not set up connect SSL_CTX"); -#endif /* HAVE_SSL */ + (void)setup_sslctxs(daemon, cfg); /* init syslog (as root) if needed, before daemonize, otherwise * a fork error could not be printed since daemonize closed stderr.*/ diff --git a/daemon/worker.c b/daemon/worker.c index 3985d1108..ba5118a74 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -2173,8 +2173,9 @@ worker_init(struct worker* worker, struct config_file *cfg, : cfg->tcp_idle_timeout, cfg->harden_large_queries, cfg->http_max_streams, cfg->http_endpoint, cfg->http_notls_downstream, - worker->daemon->tcl, worker->daemon->listen_sslctx, - worker->daemon->quic_sslctx, + worker->daemon->tcl, worker->daemon->listen_dot_sslctx, + worker->daemon->listen_doh_sslctx, + worker->daemon->listen_quic_sslctx, dtenv, worker->daemon->doq_table, worker->env.rnd, cfg, worker_handle_request, worker); if(!worker->front) { @@ -2191,7 +2192,7 @@ worker_init(struct worker* worker, struct config_file *cfg, cfg->unwanted_threshold, cfg->outgoing_tcp_mss, &worker_alloc_cleanup, worker, cfg->do_udp || cfg->udp_upstream_without_downstream, - worker->daemon->connect_sslctx, cfg->delay_close, + worker->daemon->connect_dot_sslctx, cfg->delay_close, cfg->tls_use_sni, dtenv, cfg->udp_connect, cfg->max_reuse_tcp_queries, cfg->tcp_reuse_timeout, cfg->tcp_auth_query_timeout); diff --git a/dnstap/unbound-dnstap-socket.c b/dnstap/unbound-dnstap-socket.c index 7f8be4965..cfa0c8f95 100644 --- a/dnstap/unbound-dnstap-socket.c +++ b/dnstap/unbound-dnstap-socket.c @@ -346,7 +346,8 @@ static struct tap_socket* tap_socket_new_tlsaccept(char* ip, s->fd = -1; s->ev_cb = ev_cb; s->data = data; - s->sslctx = listen_sslctx_create(server_key, server_cert, verifypem); + s->sslctx = listen_sslctx_create(server_key, server_cert, verifypem, + NULL, NULL, 0, 0, 0); if(!s->sslctx) { log_err("could not create ssl context"); free(s->ip); diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index 8a5cf2a18..2c4b28abf 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -1512,7 +1512,8 @@ listen_create(struct comm_base* base, struct listen_port* ports, size_t bufsize, int tcp_accept_count, int tcp_idle_timeout, int harden_large_queries, uint32_t http_max_streams, char* http_endpoint, int http_notls, struct tcl_list* tcp_conn_limit, - void* sslctx, void* quic_sslctx, struct dt_env* dtenv, + void* dot_sslctx, void* doh_sslctx, void* quic_sslctx, + struct dt_env* dtenv, struct doq_table* doq_table, struct ub_randstate* rnd,struct config_file* cfg, comm_point_callback_type* cb, void *cb_arg) @@ -1566,7 +1567,7 @@ listen_create(struct comm_base* base, struct listen_port* ports, ports->ftype, ports->pp2_enabled, cb, cb_arg, ports->socket); if(ports->ftype == listen_type_http) { - if(!sslctx && !http_notls) { + if(!doh_sslctx && !http_notls) { log_warn("HTTPS port configured, but " "no TLS tls-service-key or " "tls-service-pem set"); @@ -1612,8 +1613,10 @@ listen_create(struct comm_base* base, struct listen_port* ports, cp->ssl = NULL; } else if(ports->ftype == listen_type_doq) { cp->ssl = quic_sslctx; + } else if(ports->ftype == listen_type_http) { + cp->ssl = doh_sslctx; } else { - cp->ssl = sslctx; + cp->ssl = dot_sslctx; } cp->dtenv = dtenv; cp->do_not_close = 1; diff --git a/services/listen_dnsport.h b/services/listen_dnsport.h index 629f20bd4..f6275f805 100644 --- a/services/listen_dnsport.h +++ b/services/listen_dnsport.h @@ -194,7 +194,8 @@ int resolve_interface_names(char** ifs, int num_ifs, * @param http_endpoint: HTTP endpoint to service queries on * @param http_notls: no TLS for http downstream * @param tcp_conn_limit: TCP connection limit info. - * @param sslctx: nonNULL if ssl context. + * @param dot_sslctx: nonNULL if dot ssl context. + * @param doh_sslctx: nonNULL if doh ssl context. * @param quic_sslctx: nonNULL if quic ssl context. * @param dtenv: nonNULL if dnstap enabled. * @param doq_table: the doq connection table, with shared information. @@ -210,7 +211,8 @@ listen_create(struct comm_base* base, struct listen_port* ports, size_t bufsize, int tcp_accept_count, int tcp_idle_timeout, int harden_large_queries, uint32_t http_max_streams, char* http_endpoint, int http_notls, struct tcl_list* tcp_conn_limit, - void* sslctx, void* quic_sslctx, struct dt_env* dtenv, + void* dot_sslctx, void* doh_sslctx, void* quic_sslctx, + struct dt_env* dtenv, struct doq_table* doq_table, struct ub_randstate* rnd,struct config_file* cfg, comm_point_callback_type* cb, void *cb_arg); diff --git a/testcode/fake_event.c b/testcode/fake_event.c index 840a687d9..48843f1db 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -938,7 +938,8 @@ listen_create(struct comm_base* base, struct listen_port* ATTR_UNUSED(ports), char* ATTR_UNUSED(http_endpoint), int ATTR_UNUSED(http_notls), struct tcl_list* ATTR_UNUSED(tcp_conn_limit), - void* ATTR_UNUSED(sslctx), void* ATTR_UNUSED(quic_ssl), + void* ATTR_UNUSED(dot_sslctx), void* ATTR_UNUSED(doh_sslctx), + void* ATTR_UNUSED(quic_ssl), struct dt_env* ATTR_UNUSED(dtenv), struct doq_table* ATTR_UNUSED(table), struct ub_randstate* ATTR_UNUSED(rnd), diff --git a/util/net_help.c b/util/net_help.c index fbae91d9c..81499f228 100644 --- a/util/net_help.c +++ b/util/net_help.c @@ -1162,8 +1162,29 @@ log_cert(unsigned level, const char* str, void* cert) } #endif /* HAVE_SSL */ +#if defined(HAVE_SSL) && defined(HAVE_SSL_CTX_SET_ALPN_SELECT_CB) +static int +dot_alpn_select_cb(SSL* ATTR_UNUSED(ssl), const unsigned char** out, + unsigned char* outlen, const unsigned char* in, unsigned int inlen, + void* ATTR_UNUSED(arg)) +{ + static const unsigned char alpns[] = { 3, 'd', 'o', 't' }; + unsigned char* tmp_out; + int ret; + ret = SSL_select_next_proto(&tmp_out, outlen, alpns, sizeof(alpns), in, inlen); + if(ret == OPENSSL_NPN_NO_OVERLAP) { + /* Client sent ALPN but no overlap. Should have been error, + * but for privacy we continue without ALPN (e.g., if certain + * ALPNs are blocked) */ + return SSL_TLSEXT_ERR_NOACK; + } + *out = tmp_out; + return SSL_TLSEXT_ERR_OK; +} +#endif + #if defined(HAVE_SSL) && defined(HAVE_NGHTTP2) && defined(HAVE_SSL_CTX_SET_ALPN_SELECT_CB) -static int alpn_select_cb(SSL* ATTR_UNUSED(ssl), const unsigned char** out, +static int doh_alpn_select_cb(SSL* ATTR_UNUSED(ssl), const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* ATTR_UNUSED(arg)) { @@ -1177,6 +1198,23 @@ static int alpn_select_cb(SSL* ATTR_UNUSED(ssl), const unsigned char** out, } #endif +/* setup the callback for ticket keys */ +static int +setup_ticket_keys_cb(void* sslctx) +{ +# ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB + if(SSL_CTX_set_tlsext_ticket_key_evp_cb(sslctx, tls_session_ticket_key_cb) == 0) { + return 0; + } +# else + if(SSL_CTX_set_tlsext_ticket_key_cb(sslctx, tls_session_ticket_key_cb) == 0) { + return 0; + } +# endif + return 1; +} + + int listen_sslctx_setup(void* ctxt) { @@ -1248,9 +1286,6 @@ listen_sslctx_setup(void* ctxt) #ifdef HAVE_SSL_CTX_SET_SECURITY_LEVEL SSL_CTX_set_security_level(ctx, 0); #endif -#if defined(HAVE_SSL_CTX_SET_ALPN_SELECT_CB) && defined(HAVE_NGHTTP2) - SSL_CTX_set_alpn_select_cb(ctx, alpn_select_cb, NULL); -#endif #else (void)ctxt; #endif /* HAVE_SSL */ @@ -1285,7 +1320,10 @@ listen_sslctx_setup_2(void* ctxt) #endif /* HAVE_SSL */ } -void* listen_sslctx_create(char* key, char* pem, char* verifypem) +void* listen_sslctx_create(const char* key, const char* pem, + const char* verifypem, const char* tls_ciphers, + const char* tls_ciphersuites, int set_ticket_keys_cb, + int is_dot, int is_doh) { #ifdef HAVE_SSL SSL_CTX* ctx = SSL_CTX_new(SSLv23_server_method()); @@ -1336,11 +1374,50 @@ void* listen_sslctx_create(char* key, char* pem, char* verifypem) verifypem)); SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); } + if(tls_ciphers && tls_ciphers[0]) { + if (!SSL_CTX_set_cipher_list(ctx, tls_ciphers)) { + log_err("failed to set tls-cipher %s", + tls_ciphers); + log_crypto_err("Error in SSL_CTX_set_cipher_list"); + SSL_CTX_free(ctx); + return NULL; + } + } +#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES + if(tls_ciphersuites && tls_ciphersuites[0]) { + if (!SSL_CTX_set_ciphersuites(ctx, tls_ciphersuites)) { + log_err("failed to set tls-ciphersuites %s", + tls_ciphersuites); + log_crypto_err("Error in SSL_CTX_set_ciphersuites"); + SSL_CTX_free(ctx); + return NULL; + } + } +#endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */ + if(set_ticket_keys_cb) { + if(!setup_ticket_keys_cb(ctx)) { + log_crypto_err("no support for TLS session ticket"); + SSL_CTX_free(ctx); + return NULL; + } + } + /* setup ALPN */ +#if defined(HAVE_SSL_CTX_SET_ALPN_SELECT_CB) + if(is_dot) { + SSL_CTX_set_alpn_select_cb(ctx, dot_alpn_select_cb, NULL); + } else if(is_doh) { +#if defined(HAVE_NGHTTP2) + SSL_CTX_set_alpn_select_cb(ctx, doh_alpn_select_cb, NULL); +#endif + } +#endif /* HAVE_SSL_CTX_SET_ALPN_SELECT_CB */ return ctx; #else (void)key; (void)pem; (void)verifypem; + (void)tls_ciphers; (void)tls_ciphersuites; + (void)tls_session_ticket_keys; return NULL; -#endif +#endif /* HAVE_SSL */ } #ifdef USE_WINSOCK @@ -1700,7 +1777,7 @@ void ub_openssl_lock_delete(void) #endif /* OPENSSL_THREADS */ } -int listen_sslctx_setup_ticket_keys(void* sslctx, struct config_strlist* tls_session_ticket_keys) { +int listen_sslctx_setup_ticket_keys(struct config_strlist* tls_session_ticket_keys) { #ifdef HAVE_SSL size_t s = 1; struct config_strlist* p; @@ -1746,24 +1823,11 @@ int listen_sslctx_setup_ticket_keys(void* sslctx, struct config_strlist* tls_ses } /* terminate array with NULL key name entry */ keys->key_name = NULL; -# ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB - if(SSL_CTX_set_tlsext_ticket_key_evp_cb(sslctx, tls_session_ticket_key_cb) == 0) { - log_err("no support for TLS session ticket"); - return 0; - } -# else - if(SSL_CTX_set_tlsext_ticket_key_cb(sslctx, tls_session_ticket_key_cb) == 0) { - log_err("no support for TLS session ticket"); - return 0; - } -# endif return 1; #else - (void)sslctx; (void)tls_session_ticket_keys; return 0; #endif - } #ifdef HAVE_SSL diff --git a/util/net_help.h b/util/net_help.h index 4365c9a6a..278e370a2 100644 --- a/util/net_help.h +++ b/util/net_help.h @@ -493,9 +493,18 @@ void listen_sslctx_setup_2(void* ctxt); * @param key: private key file. * @param pem: public key cert. * @param verifypem: if nonNULL, verifylocation file. + * @param tls_ciphers: if non empty string, tls ciphers to use. + * @param tls_ciphersuites: if non empty string, tls ciphersuites to use. + * @param set_ticket_keys_cb: if the callback for configured ticket keys needs + * to be set. + * @param is_dot: if the TLS connection is for DoT to set the appropriate ALPN. + * @param is_doh: if the TLS connection is for DoH to set the appropriate ALPN. * return SSL_CTX* or NULL on failure (logged). */ -void* listen_sslctx_create(char* key, char* pem, char* verifypem); +void* listen_sslctx_create(const char* key, const char* pem, + const char* verifypem, const char* tls_ciphers, + const char* tls_ciphersuites, int set_ticket_keys_cb, + int is_dot, int is_doh); /** * create SSL connect context @@ -553,12 +562,10 @@ void ub_openssl_lock_delete(void); /** * setup TLS session ticket - * @param sslctx: the SSL_CTX to use (from connect_sslctx_create()) * @param tls_session_ticket_keys: TLS ticket secret filenames * @return false on failure (alloc failure). */ -int listen_sslctx_setup_ticket_keys(void* sslctx, - struct config_strlist* tls_session_ticket_keys); +int listen_sslctx_setup_ticket_keys(struct config_strlist* tls_session_ticket_keys); /** Free memory used for TLS session ticket keys */ void listen_sslctx_delete_ticket_keys(void); From 3f839cebc3fa25f5d0754f623e870c86dcc61e5c Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 20 Jan 2025 15:45:11 +0100 Subject: [PATCH 036/218] Changelog entry for #1222: - Merge #1222: Unique DoT and DoH SSL contexts to allow for different ALPN. --- doc/Changelog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 9e14e13da..050304516 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +20 January 2025: Yorgos + - Merge #1222: Unique DoT and DoH SSL contexts to allow for different + ALPN. + 15 January 2025: Yorgos - Merge #1221: Consider auth zones when checking for forwarders. From d62fff2c7ca3deca08000b623d34c0d4d9248468 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 20 Jan 2025 15:49:37 +0100 Subject: [PATCH 037/218] - Create the quic SSL listening context only when needed. --- daemon/unbound.c | 8 +++++--- doc/Changelog | 1 + util/config_file.c | 19 +++++++++++++++++++ util/config_file.h | 7 +++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/daemon/unbound.c b/daemon/unbound.c index feea43180..8de7eb0a5 100644 --- a/daemon/unbound.c +++ b/daemon/unbound.c @@ -505,9 +505,11 @@ setup_sslctxs(struct daemon* daemon, struct config_file* cfg) } #endif #ifdef HAVE_NGTCP2 - if(!(daemon->listen_quic_sslctx = quic_sslctx_create( - cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) { - fatal_exit("could not set up quic SSL_CTX"); + if(cfg_has_quic(cfg)) { + if(!(daemon->listen_quic_sslctx = quic_sslctx_create( + cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) { + fatal_exit("could not set up quic SSL_CTX"); + } } #endif /* HAVE_NGTCP2 */ } diff --git a/doc/Changelog b/doc/Changelog index 050304516..e7d8803aa 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,7 @@ 20 January 2025: Yorgos - Merge #1222: Unique DoT and DoH SSL contexts to allow for different ALPN. + - Create the quic SSL listening context only when needed. 15 January 2025: Yorgos - Merge #1221: Consider auth zones when checking for forwarders. diff --git a/util/config_file.c b/util/config_file.c index dbe1b7081..b1f0d8741 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -2866,3 +2866,22 @@ if_is_quic(const char* ifname, const char* port, int quic_port) return 0; #endif } + +/** see if config contains quic turned on */ +int +cfg_has_quic(struct config_file* cfg) +{ +#ifndef HAVE_NGTCP2 + (void)cfg; + return 0; +#else + int i; + char portbuf[32]; + snprintf(portbuf, sizeof(portbuf), "%d", cfg->port); + for(i = 0; inum_ifs; i++) { + if(if_is_quic(cfg->ifs[i], portbuf, cfg->quic_port)) + return 1; + } + return 0; +#endif +} diff --git a/util/config_file.h b/util/config_file.h index 07e539f06..6f808b960 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -1419,6 +1419,13 @@ int if_is_dnscrypt(const char* ifname, const char* port, int dnscrypt_port); /** see if interface is quic, its port number == the quic port number */ int if_is_quic(const char* ifname, const char* port, int quic_port); +/** + * Return true if the config contains settings that enable quic. + * @param cfg: config information. + * @return true if quic ports are used for server. + */ +int cfg_has_quic(struct config_file* cfg); + #ifdef USE_LINUX_IP_LOCAL_PORT_RANGE #define LINUX_IP_LOCAL_PORT_RANGE_PATH "/proc/sys/net/ipv4/ip_local_port_range" #endif From 048c193243c1f5b3d5a4e1d1cb26015e9d31c446 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 21 Jan 2025 10:04:30 +0100 Subject: [PATCH 038/218] - Use the same interface listening port discovery code for all needed protocols. - Port to string only when needed before getaddrinfo(). --- doc/Changelog | 5 ++ doc/unbound.conf.5.in | 2 +- services/listen_dnsport.c | 56 ++++++++---------- smallapp/unbound-checkconf.c | 12 ++-- util/config_file.c | 111 ++++++++++++++--------------------- util/config_file.h | 49 +++++++++++----- 6 files changed, 115 insertions(+), 120 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index e7d8803aa..8c9591884 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,8 @@ +21 January 2025: Yorgos + - Use the same interface listening port discovery code for all needed + protocols. + - Port to string only when needed before getaddrinfo(). + 20 January 2025: Yorgos - Merge #1222: Unique DoT and DoH SSL contexts to allow for different ALPN. diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 84c842f55..c5240d53a 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -716,7 +716,7 @@ and initial ACL (check if the proxy itself is denied/refused by configuration). The proxied address (if any) will then be used as the true client address and will be used where applicable for logging, ACL, DNSTAP, RPZ and IP ratelimiting. PROXYv2 is supported for UDP and TCP/TLS listening interfaces. -There is no support for PROXYv2 on a DoH or DNSCrypt listening interface. +There is no support for PROXYv2 on a DoH, DoQ or DNSCrypt listening interface. Can list multiple, each on a new statement. .TP .B quic\-port: \fI diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index 2c4b28abf..2a6c6301c 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -1034,7 +1034,7 @@ err: * Create socket from getaddrinfo results */ static int -make_sock(int stype, const char* ifname, const char* port, +make_sock(int stype, const char* ifname, int port, struct addrinfo *hints, int v6only, int* noip6, size_t rcv, size_t snd, int* reuseport, int transparent, int tcp_mss, int nodelay, int freebind, int use_systemd, int dscp, struct unbound_socket* ub_sock, @@ -1042,9 +1042,11 @@ make_sock(int stype, const char* ifname, const char* port, { struct addrinfo *res = NULL; int r, s, inuse, noproto; + char portbuf[32]; + snprintf(portbuf, sizeof(portbuf), "%d", port); hints->ai_socktype = stype; *noip6 = 0; - if((r=getaddrinfo(ifname, port, hints, &res)) != 0 || !res) { + if((r=getaddrinfo(ifname, portbuf, hints, &res)) != 0 || !res) { #ifdef USE_WINSOCK if(r == EAI_NONAME && hints->ai_family == AF_INET6){ *noip6 = 1; /* 'Host not found' for IP6 on winXP */ @@ -1052,7 +1054,7 @@ make_sock(int stype, const char* ifname, const char* port, } #endif log_err("node %s:%s getaddrinfo: %s %s", - ifname?ifname:"default", port, gai_strerror(r), + ifname?ifname:"default", portbuf, gai_strerror(r), #ifdef EAI_SYSTEM (r==EAI_SYSTEM?(char*)strerror(errno):"") #else @@ -1106,7 +1108,7 @@ make_sock(int stype, const char* ifname, const char* port, /** make socket and first see if ifname contains port override info */ static int -make_sock_port(int stype, const char* ifname, const char* port, +make_sock_port(int stype, const char* ifname, int port, struct addrinfo *hints, int v6only, int* noip6, size_t rcv, size_t snd, int* reuseport, int transparent, int tcp_mss, int nodelay, int freebind, int use_systemd, int dscp, struct unbound_socket* ub_sock, @@ -1115,23 +1117,22 @@ make_sock_port(int stype, const char* ifname, const char* port, char* s = strchr(ifname, '@'); if(s) { /* override port with ifspec@port */ - char p[16]; + int port; char newif[128]; if((size_t)(s-ifname) >= sizeof(newif)) { log_err("ifname too long: %s", ifname); *noip6 = 0; return -1; } - if(strlen(s+1) >= sizeof(p)) { - log_err("portnumber too long: %s", ifname); + port = atoi(s+1); + if(port < 0 || 0 == port || port > 65535) { + log_err("invalid portnumber in interface: %s", ifname); *noip6 = 0; return -1; } (void)strlcpy(newif, ifname, sizeof(newif)); newif[s-ifname] = 0; - (void)strlcpy(p, s+1, sizeof(p)); - p[strlen(s+1)]=0; - return make_sock(stype, newif, p, hints, v6only, noip6, rcv, + return make_sock(stype, newif, port, hints, v6only, noip6, rcv, snd, reuseport, transparent, tcp_mss, nodelay, freebind, use_systemd, dscp, ub_sock, additional); } @@ -1248,7 +1249,7 @@ set_recvpktinfo(int s, int family) * @param do_udp: if udp should be used. * @param do_tcp: if tcp should be used. * @param hints: for getaddrinfo. family and flags have to be set by caller. - * @param port: Port number to use (as string). + * @param port: Port number to use. * @param list: list of open ports, appended to, changed to point to list head. * @param rcv: receive buffer size for UDP * @param snd: send buffer size for UDP @@ -1274,7 +1275,7 @@ set_recvpktinfo(int s, int family) */ static int ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, - struct addrinfo *hints, const char* port, struct listen_port** list, + struct addrinfo *hints, int port, struct listen_port** list, size_t rcv, size_t snd, int ssl_port, struct config_strlist* tls_additional_port, int https_port, struct config_strlist* proxy_protocol_port, @@ -1287,6 +1288,7 @@ ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, int is_https = if_is_https(ifname, port, https_port); int is_dnscrypt = if_is_dnscrypt(ifname, port, dnscrypt_port); int is_pp2 = if_is_pp2(ifname, port, proxy_protocol_port); + int is_doq = if_is_quic(ifname, port, quic_port); /* Always set TCP_NODELAY on TLS connection as it speeds up the TLS * handshake. DoH had already such option so we respect it. * Otherwise the server waits before sending more handshake data for @@ -1294,7 +1296,6 @@ ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, * client waits for more data before ACKing (delayed ACK). */ int nodelay = is_https?http2_nodelay:is_ssl; struct unbound_socket* ub_sock; - int is_doq = if_is_quic(ifname, port, quic_port); const char* add = NULL; if(!do_udp && !do_tcp) @@ -1358,13 +1359,11 @@ ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, } else if(is_doq) { udp_port_type = listen_type_doq; add = "doq"; - if(((strchr(ifname, '@') && - atoi(strchr(ifname, '@')+1) == 53) || - (!strchr(ifname, '@') && atoi(port) == 53))) { - log_err("DNS over QUIC is not allowed on " - "port 53. Port 53 is for DNS " - "datagrams. Error for " - "interface '%s'.", ifname); + if(if_listens_on(ifname, port, 53, NULL)) { + log_err("DNS over QUIC is strictly not " + "allowed on port 53 as per RFC 9250. " + "Port 53 is for DNS datagrams. Error " + "for interface '%s'.", ifname); free(ub_sock->addr); free(ub_sock); return 0; @@ -1412,8 +1411,6 @@ ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, } } if(do_tcp) { - int is_ssl = if_is_ssl(ifname, port, ssl_port, - tls_additional_port); enum listen_type port_type; ub_sock = calloc(1, sizeof(struct unbound_socket)); if(!ub_sock) @@ -1881,8 +1878,6 @@ listening_ports_open(struct config_file* cfg, char** ifs, int num_ifs, struct addrinfo hints; int i, do_ip4, do_ip6; int do_tcp, do_auto; - char portbuf[32]; - snprintf(portbuf, sizeof(portbuf), "%d", cfg->port); do_ip4 = cfg->do_ip4; do_ip6 = cfg->do_ip6; do_tcp = cfg->do_tcp; @@ -1928,12 +1923,11 @@ listening_ports_open(struct config_file* cfg, char** ifs, int num_ifs, return NULL; } now = after; - snprintf(portbuf, sizeof(portbuf), "%d", extraport); if(do_ip6) { hints.ai_family = AF_INET6; if(!ports_create_if("::0", do_auto, cfg->do_udp, do_tcp, - &hints, portbuf, &list, + &hints, extraport, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, cfg->tls_additional_port, cfg->https_port, @@ -1952,7 +1946,7 @@ listening_ports_open(struct config_file* cfg, char** ifs, int num_ifs, hints.ai_family = AF_INET; if(!ports_create_if("0.0.0.0", do_auto, cfg->do_udp, do_tcp, - &hints, portbuf, &list, + &hints, extraport, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, cfg->tls_additional_port, cfg->https_port, @@ -1974,7 +1968,7 @@ listening_ports_open(struct config_file* cfg, char** ifs, int num_ifs, hints.ai_family = AF_INET6; if(!ports_create_if(do_auto?"::0":"::1", do_auto, cfg->do_udp, do_tcp, - &hints, portbuf, &list, + &hints, cfg->port, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, cfg->tls_additional_port, cfg->https_port, cfg->proxy_protocol_port, @@ -1992,7 +1986,7 @@ listening_ports_open(struct config_file* cfg, char** ifs, int num_ifs, hints.ai_family = AF_INET; if(!ports_create_if(do_auto?"0.0.0.0":"127.0.0.1", do_auto, cfg->do_udp, do_tcp, - &hints, portbuf, &list, + &hints, cfg->port, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, cfg->tls_additional_port, cfg->https_port, cfg->proxy_protocol_port, @@ -2012,7 +2006,7 @@ listening_ports_open(struct config_file* cfg, char** ifs, int num_ifs, continue; hints.ai_family = AF_INET6; if(!ports_create_if(ifs[i], 0, cfg->do_udp, - do_tcp, &hints, portbuf, &list, + do_tcp, &hints, cfg->port, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, cfg->tls_additional_port, cfg->https_port, cfg->proxy_protocol_port, @@ -2030,7 +2024,7 @@ listening_ports_open(struct config_file* cfg, char** ifs, int num_ifs, continue; hints.ai_family = AF_INET; if(!ports_create_if(ifs[i], 0, cfg->do_udp, - do_tcp, &hints, portbuf, &list, + do_tcp, &hints, cfg->port, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, cfg->tls_additional_port, cfg->https_port, cfg->proxy_protocol_port, diff --git a/smallapp/unbound-checkconf.c b/smallapp/unbound-checkconf.c index 6cc5285ec..3b7ba758a 100644 --- a/smallapp/unbound-checkconf.c +++ b/smallapp/unbound-checkconf.c @@ -342,8 +342,6 @@ interfacechecks(struct config_file* cfg) int i, j, i2, j2; char*** resif = NULL; int* num_resif = NULL; - char portbuf[32]; - snprintf(portbuf, sizeof(portbuf), "%d", cfg->port); if(cfg->num_ifs != 0) { resif = (char***)calloc(cfg->num_ifs, sizeof(char**)); @@ -366,15 +364,19 @@ interfacechecks(struct config_file* cfg) cfg->ifs[i]); } /* check for port combinations that are not supported */ - if(if_is_pp2(resif[i][0], portbuf, cfg->proxy_protocol_port)) { - if(if_is_dnscrypt(resif[i][0], portbuf, + if(if_is_pp2(resif[i][0], cfg->port, cfg->proxy_protocol_port)) { + if(if_is_dnscrypt(resif[i][0], cfg->port, cfg->dnscrypt_port)) { fatal_exit("PROXYv2 and DNSCrypt combination not " "supported!"); - } else if(if_is_https(resif[i][0], portbuf, + } else if(if_is_https(resif[i][0], cfg->port, cfg->https_port)) { fatal_exit("PROXYv2 and DoH combination not " "supported!"); + } else if(if_is_quic(resif[i][0], cfg->port, + cfg->quic_port)) { + fatal_exit("PROXYv2 and DoQ combination not " + "supported!"); } } /* search for duplicates in the returned addresses */ diff --git a/util/config_file.c b/util/config_file.c index b1f0d8741..19327c5be 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -2771,75 +2771,50 @@ int options_remote_is_address(struct config_file* cfg) return (cfg->control_ifs.first->str[0] != '/'); } -/** see if interface is https, its port number == the https port number */ int -if_is_https(const char* ifname, const char* port, int https_port) +if_listens_on(const char* ifname, int default_port, int port, + struct config_strlist* additional_ports) { + struct config_strlist* s; char* p = strchr(ifname, '@'); - if(!p && atoi(port) == https_port) - return 1; - if(p && atoi(p+1) == https_port) - return 1; - return 0; -} + int if_port; + if(p) if_port = atoi(p+1); + else if_port = default_port; -/** see if config contains https turned on */ -int cfg_has_https(struct config_file* cfg) -{ - int i; - char portbuf[32]; - snprintf(portbuf, sizeof(portbuf), "%d", cfg->port); - for(i = 0; inum_ifs; i++) { - if(if_is_https(cfg->ifs[i], portbuf, cfg->https_port)) - return 1; + if(port && if_port == port) return 1; + + for(s = additional_ports; s; s = s->next) { + if(if_port == atoi(s->str)) return 1; } return 0; } -/** see if interface is ssl, its port number == the ssl port number */ int -if_is_ssl(const char* ifname, const char* port, int ssl_port, +if_is_ssl(const char* ifname, int default_port, int ssl_port, struct config_strlist* tls_additional_port) { - struct config_strlist* s; - char* p = strchr(ifname, '@'); - if(!p && atoi(port) == ssl_port) - return 1; - if(p && atoi(p+1) == ssl_port) - return 1; - for(s = tls_additional_port; s; s = s->next) { - if(p && atoi(p+1) == atoi(s->str)) - return 1; - if(!p && atoi(port) == atoi(s->str)) - return 1; - } - return 0; + return if_listens_on(ifname, default_port, ssl_port, + tls_additional_port); } -/** see if interface is PROXYv2, its port number == the proxy port number */ int -if_is_pp2(const char* ifname, const char* port, +if_is_pp2(const char* ifname, int default_port, struct config_strlist* proxy_protocol_port) { - struct config_strlist* s; - char* p = strchr(ifname, '@'); - for(s = proxy_protocol_port; s; s = s->next) { - if(p && atoi(p+1) == atoi(s->str)) - return 1; - if(!p && atoi(port) == atoi(s->str)) - return 1; - } - return 0; + return if_listens_on(ifname, default_port, 0, proxy_protocol_port); } -/** see if interface is DNSCRYPT, its port number == the dnscrypt port number */ int -if_is_dnscrypt(const char* ifname, const char* port, int dnscrypt_port) +if_is_https(const char* ifname, int default_port, int https_port) +{ + return if_listens_on(ifname, default_port, https_port, NULL); +} + +int +if_is_dnscrypt(const char* ifname, int default_port, int dnscrypt_port) { #ifdef USE_DNSCRYPT - return ((strchr(ifname, '@') && - atoi(strchr(ifname, '@')+1) == dnscrypt_port) || - (!strchr(ifname, '@') && atoi(port) == dnscrypt_port)); + return if_listens_on(ifname, default_port, dnscrypt_port, NULL); #else (void)ifname; (void)port; @@ -2848,40 +2823,42 @@ if_is_dnscrypt(const char* ifname, const char* port, int dnscrypt_port) #endif } -/** see if interface is quic, its port number == the quic port number */ int -if_is_quic(const char* ifname, const char* port, int quic_port) +if_is_quic(const char* ifname, int default_port, int quic_port) { -#ifndef HAVE_NGTCP2 +#ifdef HAVE_NGTCP2 + return if_listens_on(ifname, default_port, quic_port, NULL); +#else (void)ifname; (void)port; (void)quic_port; return 0; -#else - char* p = strchr(ifname, '@'); - if(!p && atoi(port) == quic_port) - return 1; - if(p && atoi(p+1) == quic_port) - return 1; - return 0; #endif } -/** see if config contains quic turned on */ int -cfg_has_quic(struct config_file* cfg) +cfg_has_https(struct config_file* cfg) { -#ifndef HAVE_NGTCP2 - (void)cfg; - return 0; -#else int i; - char portbuf[32]; - snprintf(portbuf, sizeof(portbuf), "%d", cfg->port); for(i = 0; inum_ifs; i++) { - if(if_is_quic(cfg->ifs[i], portbuf, cfg->quic_port)) + if(if_is_https(cfg->ifs[i], cfg->port, cfg->https_port)) return 1; } return 0; +} + +int +cfg_has_quic(struct config_file* cfg) +{ +#ifdef HAVE_NGTCP2 + int i; + for(i = 0; inum_ifs; i++) { + if(if_is_quic(cfg->ifs[i], cfg->port, cfg->quic_port)) + return 1; + } + return 0; +#else + (void)cfg; + return 0; #endif } diff --git a/util/config_file.h b/util/config_file.h index 6f808b960..70b106ab7 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -1395,8 +1395,39 @@ void w_config_adjust_directory(struct config_file* cfg); /** debug option for unit tests. */ extern int fake_dsa, fake_sha1; -/** see if interface is https, its port number == the https port number */ -int if_is_https(const char* ifname, const char* port, int https_port); +/** Return true if interface will listen to specific port(s). + * @param ifname: the interface as configured in the configuration file. + * @param default_port: the default port to use as the interface port if ifname + * does not include a port via the '@' notation. + * @param port: port to check for, if 0 it will not be checked. + * @param additional_ports: additional configured ports, if any (nonNULL) to + * be checked against. + * @return true if one of (port, additional_ports) matches the interface port. + */ +int if_listens_on(const char* ifname, int default_port, int port, + struct config_strlist* additional_ports); + +/** see if interface will listen on https; + * its port number == the https port number */ +int if_is_https(const char* ifname, int default_port, int https_port); + +/** see if interface will listen on ssl; + * its port number == the ssl port number or any of the additional ports */ +int if_is_ssl(const char* ifname, int default_port, int ssl_port, + struct config_strlist* tls_additional_port); + +/** see if interface will listen on PROXYv2; + * its port number == any of the proxy ports number */ +int if_is_pp2(const char* ifname, int default_port, + struct config_strlist* proxy_protocol_port); + +/** see if interface will listen on DNSCRYPT; + * its port number == the dnscrypt port number */ +int if_is_dnscrypt(const char* ifname, int default_port, int dnscrypt_port); + +/** see if interface will listen on quic; + * its port number == the quic port number */ +int if_is_quic(const char* ifname, int default_port, int quic_port); /** * Return true if the config contains settings that enable https. @@ -1405,20 +1436,6 @@ int if_is_https(const char* ifname, const char* port, int https_port); */ int cfg_has_https(struct config_file* cfg); -/** see if interface is ssl, its port number == the ssl port number */ -int if_is_ssl(const char* ifname, const char* port, int ssl_port, - struct config_strlist* tls_additional_port); - -/** see if interface is PROXYv2, its port number == the proxy port number */ -int if_is_pp2(const char* ifname, const char* port, - struct config_strlist* proxy_protocol_port); - -/** see if interface is DNSCRYPT, its port number == the dnscrypt port number */ -int if_is_dnscrypt(const char* ifname, const char* port, int dnscrypt_port); - -/** see if interface is quic, its port number == the quic port number */ -int if_is_quic(const char* ifname, const char* port, int quic_port); - /** * Return true if the config contains settings that enable quic. * @param cfg: config information. From 9a0de14aa17229330719d5ff8fbbaf085c2992ba Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 21 Jan 2025 10:13:48 +0100 Subject: [PATCH 039/218] - Fix compile of interface check code when dnscrypt or quic is disabled. --- doc/Changelog | 4 ++++ util/config_file.c | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 8c9591884..90916f45b 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -3,6 +3,10 @@ protocols. - Port to string only when needed before getaddrinfo(). +21 January 2025: Wouter + - Fix compile of interface check code when dnscrypt or quic is + disabled. + 20 January 2025: Yorgos - Merge #1222: Unique DoT and DoH SSL contexts to allow for different ALPN. diff --git a/util/config_file.c b/util/config_file.c index 19327c5be..5fef48ed8 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -2817,7 +2817,7 @@ if_is_dnscrypt(const char* ifname, int default_port, int dnscrypt_port) return if_listens_on(ifname, default_port, dnscrypt_port, NULL); #else (void)ifname; - (void)port; + (void)default_port; (void)dnscrypt_port; return 0; #endif @@ -2830,7 +2830,7 @@ if_is_quic(const char* ifname, int default_port, int quic_port) return if_listens_on(ifname, default_port, quic_port, NULL); #else (void)ifname; - (void)port; + (void)default_port; (void)quic_port; return 0; #endif From 207ae97ff9c5995e974d7fdd38a3ca9974eb432b Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 21 Jan 2025 12:27:15 +0100 Subject: [PATCH 040/218] - Fix encoding of RR type ATMA. --- doc/Changelog | 1 + sldns/str2wire.c | 42 ++++++++++++++++++++++++++++++++++------- sldns/wire2str.c | 28 ++++++++++++++++++++++++++- testdata/test_ldnsrr.4 | 3 ++- testdata/test_ldnsrr.c4 | 4 ++++ 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 90916f45b..ad603e3b8 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -6,6 +6,7 @@ 21 January 2025: Wouter - Fix compile of interface check code when dnscrypt or quic is disabled. + - Fix encoding of RR type ATMA. 20 January 2025: Yorgos - Merge #1222: Unique DoT and DoH SSL contexts to allow for different diff --git a/sldns/str2wire.c b/sldns/str2wire.c index fdd40e0f2..1a956c2b9 100644 --- a/sldns/str2wire.c +++ b/sldns/str2wire.c @@ -2554,12 +2554,40 @@ int sldns_str2wire_atma_buf(const char* str, uint8_t* rd, size_t* len) { const char* s = str; size_t slen = strlen(str); - size_t dlen = 0; /* number of hexdigits parsed */ + size_t dlen = 0; /* number of hexdigits parsed for hex, + digits for E.164 */ - /* just a hex string with optional dots? */ - /* notimpl e.164 format */ if(slen > LDNS_MAX_RDFLEN*2) return LDNS_WIREPARSE_ERR_LABEL_OVERFLOW; + if(*s == 0) { + /* empty string */ + rd[0] = 0; + *len = 1; + return LDNS_WIREPARSE_ERR_OK; + } + if(s[0] == '+') { + rd[0] = 1; /* E.164 format */ + /* digits '0'..'9', with skipped dots. */ + s++; + while(*s) { + if(isspace((unsigned char)*s) || *s == '.') { + s++; + continue; + } + if(*s < '0' || *s > '9') + return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX, s-str); + if(*len < dlen + 2) + return RET_ERR(LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL, + s-str); + rd[dlen+1] = *s++; + dlen++; + } + *len = dlen+1; + return LDNS_WIREPARSE_ERR_OK; + } + + rd[0] = 0; /* AESA format */ + /* hex, with skipped dots. */ while(*s) { if(isspace((unsigned char)*s) || *s == '.') { s++; @@ -2567,17 +2595,17 @@ int sldns_str2wire_atma_buf(const char* str, uint8_t* rd, size_t* len) } if(!isxdigit((unsigned char)*s)) return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX_HEX, s-str); - if(*len < dlen/2 + 1) + if(*len < dlen/2 + 2) return RET_ERR(LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL, s-str); if((dlen&1)==0) - rd[dlen/2] = (uint8_t)sldns_hexdigit_to_int(*s++) * 16; - else rd[dlen/2] += sldns_hexdigit_to_int(*s++); + rd[dlen/2 + 1] = (uint8_t)sldns_hexdigit_to_int(*s++) * 16; + else rd[dlen/2 + 1] += sldns_hexdigit_to_int(*s++); dlen++; } if((dlen&1)!=0) return RET_ERR(LDNS_WIREPARSE_ERR_SYNTAX_HEX, s-str); - *len = dlen/2; + *len = dlen/2 + 1; return LDNS_WIREPARSE_ERR_OK; } diff --git a/sldns/wire2str.c b/sldns/wire2str.c index ff8399947..29177b535 100644 --- a/sldns/wire2str.c +++ b/sldns/wire2str.c @@ -1870,7 +1870,33 @@ int sldns_wire2str_nsap_scan(uint8_t** d, size_t* dl, char** s, size_t* sl) int sldns_wire2str_atma_scan(uint8_t** d, size_t* dl, char** s, size_t* sl) { - return print_remainder_hex("", d, dl, s, sl); + uint8_t format; + int w = 0; + size_t i; + + if(*dl < 1) return -1; + format = (*d)[0]; + (*d)+=1; + (*dl)-=1; + + if(format == 0) { + /* AESA format (ATM End System Address). */ + return print_remainder_hex("", d, dl, s, sl); + } else if(format == 1) { + /* E.164 format. */ + w += sldns_str_print(s, sl, "+"); + for(i=0; i<*dl; i++) { + if((*d)[i] < '0' || (*d)[0] > '9') + return -1; + w += sldns_str_print(s, sl, "%c", (*d)[i]); + } + (*d) += *dl; + (*dl) = 0; + } else { + /* Unknown format. */ + return -1; + } + return w; } /* internal scan routine that can modify arguments on failure */ diff --git a/testdata/test_ldnsrr.4 b/testdata/test_ldnsrr.4 index 07c9960d5..b20a31728 100644 --- a/testdata/test_ldnsrr.4 +++ b/testdata/test_ldnsrr.4 @@ -35,7 +35,8 @@ all.rr.org. IN LOC 42N 71 06 18.3W -24m 30m ; EID ; NIMLOC _http._tcp.all.rr.org. IN SRV 0 5 80 ns1.example.com. -; ATMA +atma0 IN ATMA 39.246f.00.0e7c9c.0312.0001.0001.000012345678.00 +atma1 IN ATMA +1.908.555.1212 all.rr.org. IN NAPTR 100 10 "" "" "!^urn:cid:.+@([^\\.]+\\.)(.*)$!\\2!i" . all.rr.org. IN KX 2 rt1.example.com. all.rr.org. IN CERT 6 0 0 FFsAyW1dVK7hIGuvhN56r26UwJx/ diff --git a/testdata/test_ldnsrr.c4 b/testdata/test_ldnsrr.c4 index 56b87fbcb..93e7617f6 100644 --- a/testdata/test_ldnsrr.c4 +++ b/testdata/test_ldnsrr.c4 @@ -46,6 +46,10 @@ all.rr.org. 3600 IN LOC 42 21 54.500 N 71 06 18.300 W -24m 30m 10000m 10m all.rr.org. 3600 IN LOC 42 00 00.000 N 71 06 18.300 W -24m 30m 10000m 10m 055F68747470045F74637003616C6C027272036F7267000021000100000E100017000000050050036E7331076578616D706C6503636F6D00 _http._tcp.all.rr.org. 3600 IN SRV 0 5 80 ns1.example.com. +0561746D6130000022000100000E1000150039246F000E7C9C03120001000100001234567800 +atma0. 3600 IN ATMA 39246F000E7C9C03120001000100001234567800 +0561746D6131000022000100000E10000C013139303835353531323132 +atma1. 3600 IN ATMA +19085551212 03616C6C027272036F7267000023000100000E1000290064000A000021215E75726E3A6369643A2E2B40285B5E5C2E5D2B5C2E29282E2A2924215C32216900 all.rr.org. 3600 IN NAPTR 100 10 "" "" "!^urn:cid:.+@([^\\.]+\\.)(.*)$!\\2!i" . 03616C6C027272036F7267000024000100000E100013000203727431076578616D706C6503636F6D00 From 5f58ced71efe398c8c920d79fd90f7b446d06b03 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 21 Jan 2025 12:30:30 +0100 Subject: [PATCH 041/218] - Fix to check length in ATMA string to wire. --- doc/Changelog | 1 + sldns/str2wire.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index ad603e3b8..40e739fe7 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -7,6 +7,7 @@ - Fix compile of interface check code when dnscrypt or quic is disabled. - Fix encoding of RR type ATMA. + - Fix to check length in ATMA string to wire. 20 January 2025: Yorgos - Merge #1222: Unique DoT and DoH SSL contexts to allow for different diff --git a/sldns/str2wire.c b/sldns/str2wire.c index 1a956c2b9..b3f5e9798 100644 --- a/sldns/str2wire.c +++ b/sldns/str2wire.c @@ -2559,6 +2559,8 @@ int sldns_str2wire_atma_buf(const char* str, uint8_t* rd, size_t* len) if(slen > LDNS_MAX_RDFLEN*2) return LDNS_WIREPARSE_ERR_LABEL_OVERFLOW; + if(*len < 1) + return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL; if(*s == 0) { /* empty string */ rd[0] = 0; From f822042cd027d380a5050a48c7ac1c5073dbaad5 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 21 Jan 2025 15:25:53 +0100 Subject: [PATCH 042/218] - Do not open unencrypted channels next to encrypted ones on the same port. --- doc/Changelog | 2 ++ services/listen_dnsport.c | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 40e739fe7..cc90f7cfa 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,8 @@ - Use the same interface listening port discovery code for all needed protocols. - Port to string only when needed before getaddrinfo(). + - Do not open unencrypted channels next to encrypted ones on the same + port. 21 January 2025: Wouter - Fix compile of interface check code when dnscrypt or quic is diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index 2a6c6301c..26efadc15 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -1314,6 +1314,12 @@ ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, } } + /* Check if both UDP and TCP ports should be open. + * In the case of encrypted channels, probably an unencrypted channel + * at the same port is not desired. */ + if((is_ssl || is_https) && !is_doq) do_udp = do_auto = 0; + if((is_doq) && !(is_https || is_ssl)) do_tcp = 0; + if(do_auto) { ub_sock = calloc(1, sizeof(struct unbound_socket)); if(!ub_sock) From 073c7301ebdf7511320ec817ad7ecacf6b45c4be Mon Sep 17 00:00:00 2001 From: eaglegai <31752768+eaglegai@users.noreply.github.com> Date: Tue, 21 Jan 2025 22:47:51 +0800 Subject: [PATCH 043/218] check before use daemon->shm_info (#1229) fix core after the command `unbound-control stop unbound` fix:https://github.com/NLnetLabs/unbound/issues/1228 Signed-off-by: eaglegai --- util/shm_side/shm_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/shm_side/shm_main.c b/util/shm_side/shm_main.c index 6fd1f5ea6..751d6d649 100644 --- a/util/shm_side/shm_main.c +++ b/util/shm_side/shm_main.c @@ -195,7 +195,7 @@ void shm_main_shutdown(struct daemon* daemon) { #ifdef HAVE_SHMGET /* web are OK, just disabled */ - if(!daemon->cfg->shm_enable) + if(!daemon->cfg->shm_enable || !daemon->shm_info) return; verbose(VERB_DETAIL, "SHM shutdown - KEY [%d] - ID CTL [%d] ARR [%d] - PTR CTL [%p] ARR [%p]", From d9b863ed76092b7b1ec921bc3a98b8afb62aba2f Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 21 Jan 2025 15:48:46 +0100 Subject: [PATCH 044/218] Changelog note for #1229 - Merge #1229: check before use daemon->shm_info. --- doc/Changelog | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/Changelog b/doc/Changelog index cc90f7cfa..ed6a09b5c 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -10,6 +10,7 @@ disabled. - Fix encoding of RR type ATMA. - Fix to check length in ATMA string to wire. + - Merge #1229: check before use daemon->shm_info. 20 January 2025: Yorgos - Merge #1222: Unique DoT and DoH SSL contexts to allow for different From cc55beefc82a76f8940db1cb5e3f9ba396d94304 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 21 Jan 2025 17:35:00 +0100 Subject: [PATCH 045/218] Changelog entry for #1224: - Merge #1224 from Theo Buehler: Do not use DSA API unless USE_DSA is set. --- doc/Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index ed6a09b5c..93e822d46 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -4,6 +4,8 @@ - Port to string only when needed before getaddrinfo(). - Do not open unencrypted channels next to encrypted ones on the same port. + - Merge #1224 from Theo Buehler: Do not use DSA API unless USE_DSA is + set. 21 January 2025: Wouter - Fix compile of interface check code when dnscrypt or quic is From 911509fd591bc9513960ef7652f3f505eb6eaaa6 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 24 Jan 2025 16:56:09 +0100 Subject: [PATCH 046/218] =?UTF-8?q?Changelog=20entry=20for=20#1220:=20-=20?= =?UTF-8?q?Merge=20#1220=20from=20Petr=20Men=C5=A1=C3=ADk,=20Add=20unbound?= =?UTF-8?q?=20members=20group=20access=20to=20=20=20control=20key.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/Changelog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 93e822d46..514f5c62b 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +24 January 2025: Yorgos + - Merge #1220 from Petr Menšík, Add unbound members group access to + control key. + 21 January 2025: Yorgos - Use the same interface listening port discovery code for all needed protocols. From 35dbbcb2f5db9026c4a7bd811524d6f7dc29af51 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 29 Jan 2025 12:08:28 +0100 Subject: [PATCH 047/218] - Make the default value of module-config "validator iterator" regardless of compilation options. --enable-subnet would implicitly change the value to enable the subnetcache module by default in the past. --- doc/Changelog | 6 ++++++ doc/unbound.conf.5.in | 28 +++++++++++++++++----------- util/config_file.c | 4 ---- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 514f5c62b..47fcb6604 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,9 @@ +29 January 2025: Yorgos + - Make the default value of module-config "validator iterator" + regardless of compilation options. --enable-subnet would implicitly + change the value to enable the subnetcache module by default in the + past. + 24 January 2025: Yorgos - Merge #1220 from Petr Menšík, Add unbound members group access to control key. diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index c5240d53a..cc109f0b5 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -1260,9 +1260,6 @@ Adding \fIrespip\fR to the front will cause RPZ processing to be done on all queries. The default is "\fIvalidator iterator\fR". .IP -When the server is built with -EDNS client subnet support the default is "\fIsubnetcache validator -iterator\fR". Most modules that need to be listed here have to be listed at the beginning of the line. The subnetcachedb module has to be listed just before the iterator. @@ -2511,8 +2508,8 @@ The dynamic library file to load. Repeat this option for every dynlib module instance added to the \fBmodule\-config:\fR option. .SS "DNS64 Module Options" .LP -The dns64 module must be configured in the \fBmodule\-config:\fR "dns64 -validator iterator" directive and be compiled into the daemon to be +The dns64 module must be configured in the \fBmodule\-config:\fR directive +e.g., "dns64 validator iterator" and be compiled into the daemon to be enabled. These settings go in the \fBserver:\fR section. .TP .B dns64\-prefix: \fI\fR @@ -2612,8 +2609,8 @@ in the dnscrypt nonce cache. Close to the number of cpus is a fairly good setting. .SS "EDNS Client Subnet Module Options" .LP -The ECS module must be configured in the \fBmodule\-config:\fR "subnetcache -validator iterator" directive and be compiled into the daemon to be +The ECS module must be configured in the \fBmodule\-config:\fR directive e.g., +"subnetcache validator iterator" and be compiled into the daemon to be enabled. These settings go in the \fBserver:\fR section. .LP If the destination address is allowed in the configuration Unbound will add the @@ -2634,6 +2631,15 @@ configuration file. On top of that, for each query only 100 different subnets are allowed to be stored for each address family. Exceeding that number, older entries will be purged from cache. .LP +Note that due to the nature of how EDNS Client Subnet works, by segregating the +client IP space in order to try and have tailored responses for prefixes of +unknown sizes, resolution and cache response performance are impacted as a +result. +Usage of the subnetcache module should only be enabled in installations that +require such functionality where the resolver and the clients belong to +different networks. +An example of that is an open resolver installation. +.LP This module does not interact with the \fBserve\-expired*\fR and \fBprefetch:\fR options. .TP @@ -2684,8 +2690,8 @@ Specifies the maximum number of subnets ECS answers kept in the ECS radix tree. This number applies for each qname/qclass/qtype tuple. Defaults to 100. .SS "Opportunistic IPsec Support Module Options" .LP -The IPsec module must be configured in the \fBmodule\-config:\fR "ipsecmod -validator iterator" directive and be compiled into Unbound by using +The IPsec module must be configured in the \fBmodule\-config:\fR directive +e.g., "ipsecmod validator iterator" and be compiled into Unbound by using \fB\-\-enable\-ipsecmod\fR to be enabled. These settings go in the \fBserver:\fR section. .LP @@ -2754,8 +2760,8 @@ not specified, all domains are treated as being allowed (default). Alternate syntax for \fBipsecmod\-allow\fR. .SS "Cache DB Module Options" .LP -The Cache DB module must be configured in the \fBmodule\-config:\fR -"validator cachedb iterator" directive and be compiled into the daemon +The Cache DB module must be configured in the \fBmodule\-config:\fR directive +e.g., "validator cachedb iterator" and be compiled into the daemon with \fB\-\-enable\-cachedb\fR. If this module is enabled and configured, the specified backend database works as a second level cache: diff --git a/util/config_file.c b/util/config_file.c index 5fef48ed8..595336e09 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -329,11 +329,7 @@ config_create(void) if(!(cfg->control_cert_file = strdup(RUN_DIR"/unbound_control.pem"))) goto error_exit; -#ifdef CLIENT_SUBNET - if(!(cfg->module_conf = strdup("subnetcache validator iterator"))) goto error_exit; -#else if(!(cfg->module_conf = strdup("validator iterator"))) goto error_exit; -#endif if(!(cfg->val_nsec3_key_iterations = strdup("1024 150 2048 150 4096 150"))) goto error_exit; #if defined(DNSTAP_SOCKET_PATH) From 01cea4d5be596432773591d8fce13c83618b8bae Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 30 Jan 2025 16:26:31 +0100 Subject: [PATCH 048/218] - Fix #986: Resolving sas.com with dnssec-validation fails though signed delegations seem to be (mostly) correct. --- doc/Changelog | 4 ++++ doc/unbound.conf.5.in | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index 47fcb6604..b33dfa8e8 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +30 January 2025: Wouter + - Fix #986: Resolving sas.com with dnssec-validation fails though + signed delegations seem to be (mostly) correct. + 29 January 2025: Yorgos - Make the default value of module-config "validator iterator" regardless of compilation options. --enable-subnet would implicitly diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index cc109f0b5..2e8c87e40 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -1111,7 +1111,7 @@ This works by first choosing only the strongest DS digest type as per RFC 4509 (Unbound treats the highest algorithm as the strongest) and then expecting signatures from all the advertised signing algorithms from the chosen DS(es) to be present. -If no, allows any algorithm to validate the zone. +If no, allows any one supported algorithm to validate the zone, even if other advertised algorithms are broken. Default is no. RFC 6840 mandates that zone signers must produce zones signed with all advertised algorithms, but sometimes they do not. From 72828ff81c4fad197a5b4ec7db3b4f649bccbbb9 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 17 Feb 2025 15:21:18 +0100 Subject: [PATCH 049/218] - Consider reconfigurations when calculating the still_useful_timeout for servers in the infrastructure cache. --- doc/Changelog | 4 ++++ services/cache/infra.c | 35 +++++++++++++++++++---------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index b33dfa8e8..e4d8aada0 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +17 February 2025: Yorgos + - Consider reconfigurations when calculating the still_useful_timeout + for servers in the infrastructure cache. + 30 January 2025: Wouter - Fix #986: Resolving sas.com with dnssec-validation fails though signed delegations seem to be (mostly) correct. diff --git a/services/cache/infra.c b/services/cache/infra.c index bb3ee87e9..0ed3c297b 100644 --- a/services/cache/infra.c +++ b/services/cache/infra.c @@ -60,16 +60,6 @@ * can do this number of packets (until those all timeout too) */ #define TIMEOUT_COUNT_MAX 3 -/** Minus 1000 because that is outside of the RTTBAND, so - * blacklisted servers stay blacklisted if this is chosen. - * If USEFUL_SERVER_TOP_TIMEOUT is below 1000 (configured via RTT_MAX_TIMEOUT, - * infra-cache-max-rtt) change it to just above the RTT_BAND. */ -#define STILL_USEFUL_TIMEOUT ( \ - USEFUL_SERVER_TOP_TIMEOUT < 1000 || \ - USEFUL_SERVER_TOP_TIMEOUT - 1000 <= RTT_BAND \ - ?RTT_BAND + 1 \ - :USEFUL_SERVER_TOP_TIMEOUT - 1000) - /** ratelimit value for delegation point */ int infra_dp_ratelimit = 0; @@ -82,6 +72,19 @@ int infra_ip_ratelimit = 0; * For clients with a valid DNS Cookie. */ int infra_ip_ratelimit_cookie = 0; +/** Minus 1000 because that is outside of the RTTBAND, so + * blacklisted servers stay blacklisted if this is chosen. + * If USEFUL_SERVER_TOP_TIMEOUT is below 1000 (configured via RTT_MAX_TIMEOUT, + * infra-cache-max-rtt) change it to just above the RTT_BAND. */ +static int still_useful_timeout() +{ + return + USEFUL_SERVER_TOP_TIMEOUT < 1000 || + USEFUL_SERVER_TOP_TIMEOUT - 1000 <= RTT_BAND + ?RTT_BAND + 1 + :USEFUL_SERVER_TOP_TIMEOUT - 1000; +} + size_t infra_sizefunc(void* k, void* ATTR_UNUSED(d)) { @@ -668,7 +671,7 @@ infra_update_tcp_works(struct infra_cache* infra, if(data->rtt.rto >= RTT_MAX_TIMEOUT) /* do not disqualify this server altogether, it is better * than nothing */ - data->rtt.rto = STILL_USEFUL_TIMEOUT; + data->rtt.rto = still_useful_timeout(); lock_rw_unlock(&e->lock); } @@ -808,7 +811,7 @@ infra_get_lame_rtt(struct infra_cache* infra, && infra->infra_keep_probing) { /* single probe, keep probing */ if(*rtt >= USEFUL_SERVER_TOP_TIMEOUT) - *rtt = STILL_USEFUL_TIMEOUT; + *rtt = still_useful_timeout(); } else if(host->rtt.rto >= PROBE_MAXRTO && timenow < host->probedelay && rtt_notimeout(&host->rtt)*4 <= host->rtt.rto) { /* single probe for this domain, and we are not probing */ @@ -816,15 +819,15 @@ infra_get_lame_rtt(struct infra_cache* infra, if(qtype == LDNS_RR_TYPE_A) { if(host->timeout_A >= TIMEOUT_COUNT_MAX) *rtt = USEFUL_SERVER_TOP_TIMEOUT; - else *rtt = STILL_USEFUL_TIMEOUT; + else *rtt = still_useful_timeout(); } else if(qtype == LDNS_RR_TYPE_AAAA) { if(host->timeout_AAAA >= TIMEOUT_COUNT_MAX) *rtt = USEFUL_SERVER_TOP_TIMEOUT; - else *rtt = STILL_USEFUL_TIMEOUT; + else *rtt = still_useful_timeout(); } else { if(host->timeout_other >= TIMEOUT_COUNT_MAX) *rtt = USEFUL_SERVER_TOP_TIMEOUT; - else *rtt = STILL_USEFUL_TIMEOUT; + else *rtt = still_useful_timeout(); } } /* expired entry */ @@ -832,7 +835,7 @@ infra_get_lame_rtt(struct infra_cache* infra, /* see if this can be a re-probe of an unresponsive server */ if(host->rtt.rto >= USEFUL_SERVER_TOP_TIMEOUT) { lock_rw_unlock(&e->lock); - *rtt = STILL_USEFUL_TIMEOUT; + *rtt = still_useful_timeout(); *lame = 0; *dnsseclame = 0; *reclame = 0; From 5e1f35b59b7f4d8731be6f2f2090bc2aad180301 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 19 Feb 2025 11:24:49 +0100 Subject: [PATCH 050/218] - Fix static analysis report about unhandled EOF on error conditions when reading anchor key files. --- doc/Changelog | 4 ++++ testcode/unitldns.c | 8 ++++++-- validator/val_anchor.c | 15 +++++++++------ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index e4d8aada0..435bcfa21 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +19 February 2025: Yorgos + - Fix static analysis report about unhandled EOF on error conditions + when reading anchor key files. + 17 February 2025: Yorgos - Consider reconfigurations when calculating the still_useful_timeout for servers in the infrastructure cache. diff --git a/testcode/unitldns.c b/testcode/unitldns.c index d226ee203..f8409fec7 100644 --- a/testcode/unitldns.c +++ b/testcode/unitldns.c @@ -172,10 +172,14 @@ rr_test_file(const char* input, const char* check) if(txt_in[0] == 0 || txt_in[0] == '\n' || txt_in[0] == ';') continue; /* read check lines */ - if(!fgets(wire_chk, (int)bufs, chf)) + if(!fgets(wire_chk, (int)bufs, chf)) { printf("%s too short\n", check); - if(!fgets(txt_chk, (int)bufs, chf)) + unit_assert(0); + } + if(!fgets(txt_chk, (int)bufs, chf)) { printf("%s too short\n", check); + unit_assert(0); + } chlineno += 2; if(vbmp) printf("%s:%d %s", check, chlineno-1, wire_chk); if(vbmp) printf("%s:%d %s", check, chlineno, txt_chk); diff --git a/validator/val_anchor.c b/validator/val_anchor.c index 7a5256264..ab41fa484 100644 --- a/validator/val_anchor.c +++ b/validator/val_anchor.c @@ -483,11 +483,10 @@ anchor_read_file(struct val_anchors* anchors, sldns_buffer* buffer, /** skip file to end of line */ static void -skip_to_eol(FILE* in) +skip_to_eol(FILE* in, int *c) { - int c; - while((c = getc(in)) != EOF ) { - if(c == '\n') + while((*c = getc(in)) != EOF ) { + if(*c == '\n') return; } } @@ -534,7 +533,8 @@ readkeyword_bindfile(FILE* in, sldns_buffer* buf, int* line, int comments) int numdone = 0; while((c = getc(in)) != EOF ) { if(comments && c == '#') { /* # blabla */ - skip_to_eol(in); + skip_to_eol(in, &c); + if(c == EOF) return 0; (*line)++; continue; } else if(comments && c=='/' && numdone>0 && /* /_/ bla*/ @@ -542,7 +542,8 @@ readkeyword_bindfile(FILE* in, sldns_buffer* buf, int* line, int comments) sldns_buffer_position(buf)-1) == '/') { sldns_buffer_skip(buf, -1); numdone--; - skip_to_eol(in); + skip_to_eol(in, &c); + if(c == EOF) return 0; (*line)++; continue; } else if(comments && c=='*' && numdone>0 && /* /_* bla *_/ */ @@ -559,6 +560,7 @@ readkeyword_bindfile(FILE* in, sldns_buffer* buf, int* line, int comments) if(c == '\n') (*line)++; } + if(c == EOF) return 0; continue; } /* not a comment, complete the keyword */ @@ -593,6 +595,7 @@ readkeyword_bindfile(FILE* in, sldns_buffer* buf, int* line, int comments) break; } } + if(c == EOF) return 0; return numdone; } if(is_bind_special(c)) From bcf162abd7e1f820a5c51c3638781dddae2e56e8 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 19 Feb 2025 12:14:59 +0100 Subject: [PATCH 051/218] - The maximum value of a probe rto was not aligned with the (configurable) infra-cache-max-rtt value. That could result in infra-keep-probing not working if an infra-cache-max-rtt value was chosen that was below 12000 ms. This fix still uses a default value of 12000 ms for the probe but caps it to the infra-cache-max-rtt if that is lower. --- Makefile.in | 5 +- iterator/iterator.c | 2 + services/cache/infra.c | 11 +-- services/cache/infra.h | 16 ++++ testcode/unitinfra.c | 209 +++++++++++++++++++++++++++++++++++++++++ testcode/unitmain.c | 97 ------------------- testcode/unitmain.h | 2 + util/config_file.c | 27 ++++-- util/config_file.h | 7 ++ 9 files changed, 262 insertions(+), 114 deletions(-) create mode 100644 testcode/unitinfra.c diff --git a/Makefile.in b/Makefile.in index b8d2a96b2..9262cefd4 100644 --- a/Makefile.in +++ b/Makefile.in @@ -179,11 +179,11 @@ testcode/unitlruhash.c testcode/unitmain.c testcode/unitmsgparse.c \ testcode/unitneg.c testcode/unitregional.c testcode/unitslabhash.c \ testcode/unitverify.c testcode/readhex.c testcode/testpkts.c testcode/unitldns.c \ testcode/unitecs.c testcode/unitauth.c testcode/unitzonemd.c \ -testcode/unittcpreuse.c testcode/unitdoq.c +testcode/unittcpreuse.c testcode/unitdoq.c testcode/unitinfra.c UNITTEST_OBJ=unitanchor.lo unitdname.lo unitlruhash.lo unitmain.lo \ unitmsgparse.lo unitneg.lo unitregional.lo unitslabhash.lo unitverify.lo \ readhex.lo testpkts.lo unitldns.lo unitecs.lo unitauth.lo unitzonemd.lo \ -unittcpreuse.lo unitdoq.lo +unittcpreuse.lo unitdoq.lo unitinfra.lo UNITTEST_OBJ_LINK=$(UNITTEST_OBJ) worker_cb.lo $(COMMON_OBJ) $(SLDNS_OBJ) \ $(COMPAT_OBJ) DAEMON_SRC=daemon/acl_list.c daemon/cachedump.c daemon/daemon.c \ @@ -1261,6 +1261,7 @@ unitzonemd.lo unitzonemd.o: $(srcdir)/testcode/unitzonemd.c config.h $(srcdir)/u $(srcdir)/validator/val_anchor.h unittcpreuse.lo unittcpreuse.o: $(srcdir)/testcode/unittcpreuse.c config.h $(srcdir)/services/outside_network.h \ $(srcdir)/util/random.h +unitinfra.lo unitinfra.o: $(srcdir)/testcode/unitinfra.c config.h $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h $(srcdir)/iterator/iterator.h acl_list.lo acl_list.o: $(srcdir)/daemon/acl_list.c config.h $(srcdir)/daemon/acl_list.h \ $(srcdir)/util/storage/dnstree.h $(srcdir)/util/rbtree.h $(srcdir)/services/view.h $(srcdir)/util/locks.h \ $(srcdir)/util/log.h $(srcdir)/util/regional.h $(srcdir)/util/config_file.h $(srcdir)/util/net_help.h \ diff --git a/iterator/iterator.c b/iterator/iterator.c index ec70a267b..a0ddb296b 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -78,6 +78,8 @@ int UNKNOWN_SERVER_NICENESS = 376; int USEFUL_SERVER_TOP_TIMEOUT = 120000; /* Equals USEFUL_SERVER_TOP_TIMEOUT*4 */ int BLACKLIST_PENALTY = (120000*4); +/** Timeout when only a single probe query per IP is allowed. */ +int PROBE_MAXRTO = PROBE_MAXRTO_DEFAULT; /* in msec */ static void target_count_increase_nx(struct iter_qstate* iq, int num); diff --git a/services/cache/infra.c b/services/cache/infra.c index 0ed3c297b..3833c1a09 100644 --- a/services/cache/infra.c +++ b/services/cache/infra.c @@ -52,14 +52,6 @@ #include "util/config_file.h" #include "iterator/iterator.h" -/** Timeout when only a single probe query per IP is allowed. */ -#define PROBE_MAXRTO 12000 /* in msec */ - -/** number of timeouts for a type when the domain can be blocked ; - * even if another type has completely rtt maxed it, the different type - * can do this number of packets (until those all timeout too) */ -#define TIMEOUT_COUNT_MAX 3 - /** ratelimit value for delegation point */ int infra_dp_ratelimit = 0; @@ -76,7 +68,8 @@ int infra_ip_ratelimit_cookie = 0; * blacklisted servers stay blacklisted if this is chosen. * If USEFUL_SERVER_TOP_TIMEOUT is below 1000 (configured via RTT_MAX_TIMEOUT, * infra-cache-max-rtt) change it to just above the RTT_BAND. */ -static int still_useful_timeout() +int +still_useful_timeout() { return USEFUL_SERVER_TOP_TIMEOUT < 1000 || diff --git a/services/cache/infra.h b/services/cache/infra.h index 1a88bbb94..752a141a8 100644 --- a/services/cache/infra.h +++ b/services/cache/infra.h @@ -52,6 +52,19 @@ struct slabhash; struct config_file; +/** number of timeouts for a type when the domain can be blocked ; + * even if another type has completely rtt maxed it, the different type + * can do this number of packets (until those all timeout too) */ +#define TIMEOUT_COUNT_MAX 3 + + +/** Timeout when only a single probe query per IP is allowed. + * Any RTO above this number is considered a probe. + * It is synchronized (caped) with USEFUL_SERVER_TOP_TIMEOUT so that probing + * keeps working even if that configurable number drops below the default + * 12000 ms of probing. */ +extern int PROBE_MAXRTO; + /** * Host information kept for every server, per zone. */ @@ -502,4 +515,7 @@ void infra_wait_limit_inc(struct infra_cache* infra, struct comm_reply* rep, void infra_wait_limit_dec(struct infra_cache* infra, struct comm_reply* rep, struct config_file* cfg); +/** exported for unit test */ +int still_useful_timeout(); + #endif /* SERVICES_CACHE_INFRA_H */ diff --git a/testcode/unitinfra.c b/testcode/unitinfra.c new file mode 100644 index 000000000..6834c51ee --- /dev/null +++ b/testcode/unitinfra.c @@ -0,0 +1,209 @@ +/* + * testcode/unitinfra.c - unit test for infra cache. + * + * Copyright (c) 2025, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +/** + * \file + * Tests the infra functionality. + */ + +#include "config.h" +#include "testcode/unitmain.h" +#include "iterator/iterator.h" +#include "services/cache/infra.h" +#include "util/config_file.h" +#include "util/net_help.h" + +/* lookup and get key and data structs easily */ +static struct infra_data* infra_lookup_host(struct infra_cache* infra, + struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, + size_t zonelen, int wr, time_t now, struct infra_key** k) +{ + struct infra_data* d; + struct lruhash_entry* e = infra_lookup_nottl(infra, addr, addrlen, + zone, zonelen, wr); + if(!e) return NULL; + d = (struct infra_data*)e->data; + if(d->ttl < now) { + lock_rw_unlock(&e->lock); + return NULL; + } + *k = (struct infra_key*)e->key; + return d; +} + +static void test_keep_probing(struct infra_cache* slab, + struct config_file* cfg, struct sockaddr_storage one, socklen_t onelen, + uint8_t* zone, size_t zonelen, time_t *now, int keep_probing, + int rtt_max_timeout) +{ + uint8_t edns_lame; + int vs, to, lame, dnsseclame, reclame, probedelay; + struct infra_key* k; + struct infra_data* d; + + /* configure */ + cfg->infra_cache_max_rtt = rtt_max_timeout; + config_apply_max_rtt(rtt_max_timeout); + slab->infra_keep_probing = keep_probing; + + /* expired previous entry */ + *now += cfg->host_ttl + 10; + unit_assert( infra_host(slab, &one, onelen, zone, zonelen, + *now, &vs, &edns_lame, &to) ); + + /* simulate timeouts until the USEFUL_SERVER_TOP_TIMEOUT is reached */ + while(to < USEFUL_SERVER_TOP_TIMEOUT) { + unit_assert( infra_rtt_update(slab, &one, onelen, zone, zonelen, + LDNS_RR_TYPE_A, -1, to, *now) ); + unit_assert( infra_host(slab, &one, onelen, zone, zonelen, + *now, &vs, &edns_lame, &to) ); + unit_assert( vs == 0 && to <= USEFUL_SERVER_TOP_TIMEOUT && edns_lame == 0 ); + } + unit_assert( vs == 0 && to == USEFUL_SERVER_TOP_TIMEOUT && edns_lame == 0 ); + + /* don't let the record expire */ + unit_assert( (d=infra_lookup_host(slab, &one, onelen, zone, zonelen, 0, *now, &k)) ); + unit_assert( d->timeout_A >= TIMEOUT_COUNT_MAX ); + unit_assert( d->probedelay > 0 ); + probedelay = d->probedelay; + lock_rw_unlock(&k->entry.lock); + cfg->host_ttl = cfg->host_ttl + *now < probedelay + ?cfg->host_ttl :probedelay + 10; + + /* advance time and check that probing is as expected; we already had a + * lot of A timeouts (checked above). */ + *now = probedelay; + unit_assert( infra_get_lame_rtt(slab, &one, onelen, zone, zonelen, + LDNS_RR_TYPE_A, &lame, &dnsseclame, &reclame, &to, *now) ); + unit_assert( lame == 0 && dnsseclame == 0 && reclame == 0 + && to == keep_probing ?still_useful_timeout() :USEFUL_SERVER_TOP_TIMEOUT); +} + +/** test host cache */ +void infra_test(void) +{ + struct sockaddr_storage one; + socklen_t onelen; + uint8_t* zone = (uint8_t*)"\007example\003com\000"; + size_t zonelen = 13; + struct infra_cache* slab; + struct config_file* cfg = config_create(); + time_t now = 0; + uint8_t edns_lame; + int vs, to; + struct infra_key* k; + struct infra_data* d; + int init = UNKNOWN_SERVER_NICENESS; + int default_max_rtt = USEFUL_SERVER_TOP_TIMEOUT; + + unit_show_feature("infra cache"); + unit_assert(ipstrtoaddr("127.0.0.1", 53, &one, &onelen)); + + slab = infra_create(cfg); + /* insert new record */ + unit_assert( infra_host(slab, &one, onelen, zone, zonelen, now, + &vs, &edns_lame, &to) ); + unit_assert( vs == 0 && to == init && edns_lame == 0 ); + + /* simulate no answer */ + unit_assert( infra_rtt_update(slab, &one, onelen, zone, zonelen, LDNS_RR_TYPE_A, -1, init, now) ); + unit_assert( infra_host(slab, &one, onelen, zone, zonelen, + now, &vs, &edns_lame, &to) ); + unit_assert( vs == 0 && to == init*2 && edns_lame == 0 ); + + /* simulate EDNS lame */ + unit_assert( infra_edns_update(slab, &one, onelen, zone, zonelen, -1, now) ); + unit_assert( infra_host(slab, &one, onelen, zone, zonelen, + now, &vs, &edns_lame, &to) ); + unit_assert( vs == -1 && to == init*2 && edns_lame == 1); + + /* simulate cache expiry */ + now += cfg->host_ttl + 10; + unit_assert( infra_host(slab, &one, onelen, zone, zonelen, + now, &vs, &edns_lame, &to) ); + unit_assert( vs == 0 && to == init && edns_lame == 0 ); + + /* simulate no lame answer */ + unit_assert( infra_set_lame(slab, &one, onelen, + zone, zonelen, now, 0, 0, LDNS_RR_TYPE_A) ); + unit_assert( (d=infra_lookup_host(slab, &one, onelen, zone, zonelen, 0, now, &k)) ); + unit_assert( d->ttl == now+cfg->host_ttl ); + unit_assert( d->edns_version == 0 ); + unit_assert(!d->isdnsseclame && !d->rec_lame && d->lame_type_A && + !d->lame_other); + lock_rw_unlock(&k->entry.lock); + + /* test merge of data */ + unit_assert( infra_set_lame(slab, &one, onelen, + zone, zonelen, now, 0, 0, LDNS_RR_TYPE_AAAA) ); + unit_assert( (d=infra_lookup_host(slab, &one, onelen, zone, zonelen, 0, now, &k)) ); + unit_assert(!d->isdnsseclame && !d->rec_lame && d->lame_type_A && + d->lame_other); + lock_rw_unlock(&k->entry.lock); + + /* test that noEDNS cannot overwrite known-yesEDNS */ + now += cfg->host_ttl + 10; + unit_assert( infra_host(slab, &one, onelen, zone, zonelen, + now, &vs, &edns_lame, &to) ); + unit_assert( vs == 0 && to == init && edns_lame == 0 ); + + unit_assert( infra_edns_update(slab, &one, onelen, zone, zonelen, 0, now) ); + unit_assert( infra_host(slab, &one, onelen, zone, zonelen, + now, &vs, &edns_lame, &to) ); + unit_assert( vs == 0 && to == init && edns_lame == 1 ); + + unit_assert( infra_edns_update(slab, &one, onelen, zone, zonelen, -1, now) ); + unit_assert( infra_host(slab, &one, onelen, zone, zonelen, + now, &vs, &edns_lame, &to) ); + unit_assert( vs == 0 && to == init && edns_lame == 1 ); + + unit_show_feature("infra cache probing (keep-probing off, default infra-cache-max-rtt)"); + test_keep_probing(slab, cfg, one, onelen, zone, zonelen, &now, 0, default_max_rtt); + + unit_show_feature("infra cache probing (keep-probing on, default infra-cache-max-rtt)"); + test_keep_probing(slab, cfg, one, onelen, zone, zonelen, &now, 1, default_max_rtt); + + unit_show_feature("infra cache probing (keep-probing off, low infra-cache-max-rtt)"); + test_keep_probing(slab, cfg, one, onelen, zone, zonelen, &now, 0, 3000); + + unit_show_feature("infra cache probing (keep-probing on, low infra-cache-max-rtt)"); + test_keep_probing(slab, cfg, one, onelen, zone, zonelen, &now, 1, 3000); + + /* Re-apply defaults for other unit tests that follow */ + config_apply_max_rtt(default_max_rtt); + + infra_delete(slab); + config_delete(cfg); +} diff --git a/testcode/unitmain.c b/testcode/unitmain.c index 653d3efbe..334c1af93 100644 --- a/testcode/unitmain.c +++ b/testcode/unitmain.c @@ -433,103 +433,6 @@ rtt_test(void) unit_assert(UB_STATS_BUCKET_NUM == NUM_BUCKETS_HIST); } -#include "services/cache/infra.h" - -/* lookup and get key and data structs easily */ -static struct infra_data* infra_lookup_host(struct infra_cache* infra, - struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, - size_t zonelen, int wr, time_t now, struct infra_key** k) -{ - struct infra_data* d; - struct lruhash_entry* e = infra_lookup_nottl(infra, addr, addrlen, - zone, zonelen, wr); - if(!e) return NULL; - d = (struct infra_data*)e->data; - if(d->ttl < now) { - lock_rw_unlock(&e->lock); - return NULL; - } - *k = (struct infra_key*)e->key; - return d; -} - -/** test host cache */ -static void -infra_test(void) -{ - struct sockaddr_storage one; - socklen_t onelen; - uint8_t* zone = (uint8_t*)"\007example\003com\000"; - size_t zonelen = 13; - struct infra_cache* slab; - struct config_file* cfg = config_create(); - time_t now = 0; - uint8_t edns_lame; - int vs, to; - struct infra_key* k; - struct infra_data* d; - int init = 376; - - unit_show_feature("infra cache"); - unit_assert(ipstrtoaddr("127.0.0.1", 53, &one, &onelen)); - - slab = infra_create(cfg); - unit_assert( infra_host(slab, &one, onelen, zone, zonelen, now, - &vs, &edns_lame, &to) ); - unit_assert( vs == 0 && to == init && edns_lame == 0 ); - - unit_assert( infra_rtt_update(slab, &one, onelen, zone, zonelen, LDNS_RR_TYPE_A, -1, init, now) ); - unit_assert( infra_host(slab, &one, onelen, zone, zonelen, - now, &vs, &edns_lame, &to) ); - unit_assert( vs == 0 && to == init*2 && edns_lame == 0 ); - - unit_assert( infra_edns_update(slab, &one, onelen, zone, zonelen, -1, now) ); - unit_assert( infra_host(slab, &one, onelen, zone, zonelen, - now, &vs, &edns_lame, &to) ); - unit_assert( vs == -1 && to == init*2 && edns_lame == 1); - - now += cfg->host_ttl + 10; - unit_assert( infra_host(slab, &one, onelen, zone, zonelen, - now, &vs, &edns_lame, &to) ); - unit_assert( vs == 0 && to == init && edns_lame == 0 ); - - unit_assert( infra_set_lame(slab, &one, onelen, - zone, zonelen, now, 0, 0, LDNS_RR_TYPE_A) ); - unit_assert( (d=infra_lookup_host(slab, &one, onelen, zone, zonelen, 0, now, &k)) ); - unit_assert( d->ttl == now+cfg->host_ttl ); - unit_assert( d->edns_version == 0 ); - unit_assert(!d->isdnsseclame && !d->rec_lame && d->lame_type_A && - !d->lame_other); - lock_rw_unlock(&k->entry.lock); - - /* test merge of data */ - unit_assert( infra_set_lame(slab, &one, onelen, - zone, zonelen, now, 0, 0, LDNS_RR_TYPE_AAAA) ); - unit_assert( (d=infra_lookup_host(slab, &one, onelen, zone, zonelen, 0, now, &k)) ); - unit_assert(!d->isdnsseclame && !d->rec_lame && d->lame_type_A && - d->lame_other); - lock_rw_unlock(&k->entry.lock); - - /* test that noEDNS cannot overwrite known-yesEDNS */ - now += cfg->host_ttl + 10; - unit_assert( infra_host(slab, &one, onelen, zone, zonelen, - now, &vs, &edns_lame, &to) ); - unit_assert( vs == 0 && to == init && edns_lame == 0 ); - - unit_assert( infra_edns_update(slab, &one, onelen, zone, zonelen, 0, now) ); - unit_assert( infra_host(slab, &one, onelen, zone, zonelen, - now, &vs, &edns_lame, &to) ); - unit_assert( vs == 0 && to == init && edns_lame == 1 ); - - unit_assert( infra_edns_update(slab, &one, onelen, zone, zonelen, -1, now) ); - unit_assert( infra_host(slab, &one, onelen, zone, zonelen, - now, &vs, &edns_lame, &to) ); - unit_assert( vs == 0 && to == init && edns_lame == 1 ); - - infra_delete(slab); - config_delete(cfg); -} - #include "util/edns.h" /* Complete version-invalid client cookie; needs a new one. * Based on edns_cookie_rfc9018_a2 */ diff --git a/testcode/unitmain.h b/testcode/unitmain.h index 99d5240d2..00b5a6220 100644 --- a/testcode/unitmain.h +++ b/testcode/unitmain.h @@ -86,5 +86,7 @@ void zonemd_test(void); void tcpreuse_test(void); /** unit test for doq functions */ void doq_test(void); +/** unit test for infra cache functions */ +void infra_test(void); #endif /* TESTCODE_UNITMAIN_H */ diff --git a/util/config_file.c b/util/config_file.c index 595336e09..f6e25a1ea 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -498,6 +498,25 @@ struct config_file* config_create_forlib(void) #define S_STRLIST_APPEND(str, var) if(strcmp(opt, str)==0) \ { return cfg_strlist_append(&cfg->var, strdup(val)); } +/** Set PROBE_MAXRTO based on current RTT_MAX_TIMEOUT + * (USEFUL_SERVER_TOP_TIMEOUT) configuration. */ +static int +probe_maxrto(int useful_server_top_timeout) { + return + PROBE_MAXRTO > useful_server_top_timeout + ?useful_server_top_timeout + :PROBE_MAXRTO_DEFAULT; +} + +/** Apply the relevant changes that rely upon RTT_MAX_TIMEOUT */ +int config_apply_max_rtt(int max_rtt) +{ + USEFUL_SERVER_TOP_TIMEOUT = max_rtt; + BLACKLIST_PENALTY = max_rtt*4; + PROBE_MAXRTO = probe_maxrto(max_rtt); + return max_rtt; +} + int config_set_option(struct config_file* cfg, const char* opt, const char* val) { @@ -644,9 +663,7 @@ int config_set_option(struct config_file* cfg, const char* opt, } else if(strcmp(opt, "infra-cache-max-rtt:") == 0) { IS_NUMBER_OR_ZERO; cfg->infra_cache_max_rtt = atoi(val); - RTT_MAX_TIMEOUT=cfg->infra_cache_max_rtt; - USEFUL_SERVER_TOP_TIMEOUT = RTT_MAX_TIMEOUT; - BLACKLIST_PENALTY = USEFUL_SERVER_TOP_TIMEOUT*4; + RTT_MAX_TIMEOUT=config_apply_max_rtt(cfg->infra_cache_max_rtt); } else S_YNO("infra-keep-probing:", infra_keep_probing) else S_NUMBER_OR_ZERO("infra-host-ttl:", host_ttl) @@ -2410,15 +2427,13 @@ config_apply(struct config_file* config) MAX_NEG_TTL = (time_t)config->max_negative_ttl; MIN_NEG_TTL = (time_t)config->min_negative_ttl; RTT_MIN_TIMEOUT = config->infra_cache_min_rtt; - RTT_MAX_TIMEOUT = config->infra_cache_max_rtt; + RTT_MAX_TIMEOUT = config_apply_max_rtt(config->infra_cache_max_rtt); EDNS_ADVERTISED_SIZE = (uint16_t)config->edns_buffer_size; MINIMAL_RESPONSES = config->minimal_responses; RRSET_ROUNDROBIN = config->rrset_roundrobin; LOG_TAG_QUERYREPLY = config->log_tag_queryreply; MAX_GLOBAL_QUOTA = config->max_global_quota; UNKNOWN_SERVER_NICENESS = config->unknown_server_time_limit; - USEFUL_SERVER_TOP_TIMEOUT = RTT_MAX_TIMEOUT; - BLACKLIST_PENALTY = USEFUL_SERVER_TOP_TIMEOUT*4; log_set_time_asc(config->log_time_ascii); log_set_time_iso(config->log_time_iso); autr_permit_small_holddown = config->permit_small_holddown; diff --git a/util/config_file.h b/util/config_file.h index 70b106ab7..71e9cad2e 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -54,6 +54,9 @@ struct sock_list; struct ub_packed_rrset_key; struct regional; +/** Default value for PROBE_MAXRTO */ +#define PROBE_MAXRTO_DEFAULT 12000 + /** List head for strlist processing, used for append operation. */ struct config_strlist_head { /** first in list of text items */ @@ -976,6 +979,10 @@ void config_delete(struct config_file* config); */ void config_apply(struct config_file* config); +/** Apply the relevant changes that rely upon RTT_MAX_TIMEOUT; + * exported for unit test */ +int config_apply_max_rtt(int max_rtt); + /** * Find username, sets cfg_uid and cfg_gid. * @param config: the config structure. From 1894c0a1505c6791d6c9f6e77b7ff47cfc1f1545 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 19 Feb 2025 13:46:01 +0100 Subject: [PATCH 052/218] Changelog entry for #1241: - Merge #1241: Fix infra-keep-probing for low infra-cache-max-rtt values. --- doc/Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 435bcfa21..7ed4c031b 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,8 @@ 19 February 2025: Yorgos - Fix static analysis report about unhandled EOF on error conditions when reading anchor key files. + - Merge #1241: Fix infra-keep-probing for low infra-cache-max-rtt + values. 17 February 2025: Yorgos - Consider reconfigurations when calculating the still_useful_timeout From c5c54862617c5ea2389736156fb84ad7efb73df8 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 24 Feb 2025 14:47:13 +0100 Subject: [PATCH 053/218] - Fix hash calculation for cachedb to ignore case. Previously, cached records there were only relevant for same case queries (if not already in Unbound's internal cache). --- cachedb/cachedb.c | 2 ++ doc/Changelog | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/cachedb/cachedb.c b/cachedb/cachedb.c index eca3b7cb7..bdb1754e4 100644 --- a/cachedb/cachedb.c +++ b/cachedb/cachedb.c @@ -47,6 +47,7 @@ #include "util/regional.h" #include "util/net_help.h" #include "util/config_file.h" +#include "util/data/dname.h" #include "util/data/msgreply.h" #include "util/data/msgencode.h" #include "services/cache/dns.h" @@ -341,6 +342,7 @@ calc_hash(struct query_info* qinfo, struct module_env* env, char* buf, /* copy the hash info into the clear buffer */ if(clen + qinfo->qname_len < sizeof(clear)) { memmove(clear+clen, qinfo->qname, qinfo->qname_len); + query_dname_tolower(clear+clen); clen += qinfo->qname_len; } if(clen + 4 < sizeof(clear)) { diff --git a/doc/Changelog b/doc/Changelog index 7ed4c031b..330d9145b 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,8 @@ +24 February 2025: Yorgos + - Fix hash calculation for cachedb to ignore case. Previously, cached + records there were only relevant for same case queries (if not + already in Unbound's internal cache). + 19 February 2025: Yorgos - Fix static analysis report about unhandled EOF on error conditions when reading anchor key files. From c371be3f9f8a59e6259d037bbcca7fcc4ffb5b7a Mon Sep 17 00:00:00 2001 From: Florian Obser Date: Tue, 25 Feb 2025 09:36:44 +0100 Subject: [PATCH 054/218] Do not shadow tm on line 236. (#1243) --- util/log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/log.c b/util/log.c index a44758728..b75cf065f 100644 --- a/util/log.c +++ b/util/log.c @@ -281,7 +281,7 @@ log_vmsg(int pri, const char* type, if(log_time_iso && log_time_asc) { char tzbuf[16]; struct timeval tv; - struct tm tm, *tm_p; + struct tm *tm_p; if(gettimeofday(&tv, NULL) < 0) memset(&tv, 0, sizeof(tv)); now = (time_t)tv.tv_sec; From a3d750b7d84628a0f585da3e0f889058542ac6df Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 25 Feb 2025 09:37:36 +0100 Subject: [PATCH 055/218] Changelog note for #1243 - Merge #1243: Do not shadow tm on line 236. --- doc/Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 330d9145b..4c0fb1711 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +25 February 2025: Wouter + - Merge #1243: Do not shadow tm on line 236. + 24 February 2025: Yorgos - Fix hash calculation for cachedb to ignore case. Previously, cached records there were only relevant for same case queries (if not From d9f1dae54012879f06aac16e30027caa728fe551 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 28 Feb 2025 09:22:19 +0100 Subject: [PATCH 056/218] Prefer SOURCE_DATE_EPOCH over actual time (#1238) * Add ax_build_date_epoch from Autoconf Archive Signed-off-by: Sefa Eyeoglu * Prefer SOURCE_DATE_EPOCH over actual time Signed-off-by: Sefa Eyeoglu --------- Signed-off-by: Sefa Eyeoglu --- ax_build_date_epoch.m4 | 70 ++++++++++++++++++++++++++++++++++++++++++ configure | 40 ++++++++++++++++++++++-- configure.ac | 6 ++-- 3 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 ax_build_date_epoch.m4 diff --git a/ax_build_date_epoch.m4 b/ax_build_date_epoch.m4 new file mode 100644 index 000000000..dbecb067a --- /dev/null +++ b/ax_build_date_epoch.m4 @@ -0,0 +1,70 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_build_date_epoch.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_BUILD_DATE_EPOCH(VARIABLE[, FORMAT[, ACTION-IF-FAIL]]) +# +# DESCRIPTION +# +# Sets VARIABLE to a string representing the current time. It is +# formatted according to FORMAT if specified, otherwise it is formatted as +# the number of seconds (excluding leap seconds) since the UNIX epoch (01 +# Jan 1970 00:00:00 UTC). +# +# If the SOURCE_DATE_EPOCH environment variable is set, it uses the value +# of that variable instead of the current time. See +# https://reproducible-builds.org/specs/source-date-epoch). If +# SOURCE_DATE_EPOCH is set but cannot be properly interpreted as a UNIX +# timestamp, then execute ACTION-IF-FAIL if specified, otherwise error. +# +# VARIABLE is AC_SUBST-ed. +# +# LICENSE +# +# Copyright (c) 2016 Eric Bavier +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 2 + +AC_DEFUN([AX_BUILD_DATE_EPOCH], +[dnl +AC_MSG_CHECKING([for build time]) +ax_date_fmt="m4_default($2,%s)" +AS_IF([test x"$SOURCE_DATE_EPOCH" = x], + [$1=`date "+$ax_date_fmt"`], + [ax_build_date=`date -u -d "@$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null \ + || date -u -r "$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null` + AS_IF([test x"$ax_build_date" = x], + [m4_ifval([$3], + [$3], + [AC_MSG_ERROR([malformed SOURCE_DATE_EPOCH])])], + [$1=$ax_build_date])]) +AC_MSG_RESULT([$$1]) +])dnl AX_BUILD_DATE_EPOCH diff --git a/configure b/configure index a41e3e1eb..189ef0c57 100755 --- a/configure +++ b/configure @@ -19894,7 +19894,25 @@ if test "`uname`" = "Linux"; then GCC_DOCKER_LINTFLAGS='-syntax' fi -CONFIG_DATE=`date +%Y%m%d` +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5 +printf %s "checking for build time... " >&6; } +ax_date_fmt="%Y%m%d" +if test x"$SOURCE_DATE_EPOCH" = x +then : + CONFIG_DATE=`date "+$ax_date_fmt"` +else $as_nop + ax_build_date=`date -u -d "@$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null \ + || date -u -r "$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null` + if test x"$ax_build_date" = x +then : + as_fn_error $? "malformed SOURCE_DATE_EPOCH" "$LINENO" 5 +else $as_nop + CONFIG_DATE=$ax_build_date +fi +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CONFIG_DATE" >&5 +printf "%s\n" "$CONFIG_DATE" >&6; } + # Checks for libraries. @@ -25000,7 +25018,25 @@ printf "%s\n" "#define MAXSYSLOGMSGLEN 10240" >>confdefs.h version=1.22.1 -date=`date +'%b %e, %Y'` +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5 +printf %s "checking for build time... " >&6; } +ax_date_fmt="%b %e, %Y" +if test x"$SOURCE_DATE_EPOCH" = x +then : + date=`date "+$ax_date_fmt"` +else $as_nop + ax_build_date=`date -u -d "@$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null \ + || date -u -r "$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null` + if test x"$ax_build_date" = x +then : + as_fn_error $? "malformed SOURCE_DATE_EPOCH" "$LINENO" 5 +else $as_nop + date=$ax_build_date +fi +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $date" >&5 +printf "%s\n" "$date" >&6; } + ac_config_files="$ac_config_files Makefile doc/example.conf doc/libunbound.3 doc/unbound.8 doc/unbound-anchor.8 doc/unbound-checkconf.8 doc/unbound.conf.5 doc/unbound-control.8 doc/unbound-host.1 smallapp/unbound-control-setup.sh dnstap/dnstap_config.h dnscrypt/dnscrypt_config.h contrib/libunbound.pc contrib/unbound.socket contrib/unbound.service contrib/unbound_portable.service" diff --git a/configure.ac b/configure.ac index a5a856fc1..404cc98ef 100644 --- a/configure.ac +++ b/configure.ac @@ -2,6 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.56]) sinclude(acx_nlnetlabs.m4) +sinclude(ax_build_date_epoch.m4) sinclude(ax_pthread.m4) sinclude(acx_python.m4) sinclude(ax_pkg_swig.m4) @@ -908,7 +909,7 @@ if test "`uname`" = "Linux"; then GCC_DOCKER_LINTFLAGS='-syntax' AC_SUBST(GCC_DOCKER_LINTFLAGS) fi -CONFIG_DATE=`date +%Y%m%d` +AX_BUILD_DATE_EPOCH(CONFIG_DATE, [%Y%m%d]) AC_SUBST(CONFIG_DATE) # Checks for libraries. @@ -2435,7 +2436,8 @@ char *unbound_stat_strdup_log(const char *s, const char* file, int line, dnl if we build from source tree, the man pages need @date@ and @version@ dnl if this is a distro tarball, that was already done by makedist.sh AC_SUBST(version, [VERSION_MAJOR.VERSION_MINOR.VERSION_MICRO]) -AC_SUBST(date, [`date +'%b %e, %Y'`]) +AX_BUILD_DATE_EPOCH(date, [[%b %e, %Y]]) +AC_SUBST(date) AC_CONFIG_FILES([Makefile doc/example.conf doc/libunbound.3 doc/unbound.8 doc/unbound-anchor.8 doc/unbound-checkconf.8 doc/unbound.conf.5 doc/unbound-control.8 doc/unbound-host.1 smallapp/unbound-control-setup.sh dnstap/dnstap_config.h dnscrypt/dnscrypt_config.h contrib/libunbound.pc contrib/unbound.socket contrib/unbound.service contrib/unbound_portable.service]) AC_CONFIG_HEADERS([config.h]) From 5c84bb573f9728c10bcb3592dbd12be403d362de Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 28 Feb 2025 09:32:49 +0100 Subject: [PATCH 057/218] Changelog nore for #1238 and add `--help` description. - Merge #1238: Prefer SOURCE_DATE_EPOCH over actual time. Add --help output description for the SOURCE_DATE_EPOCH variable. --- configure | 7 +++++++ configure.ac | 1 + doc/Changelog | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/configure b/configure index 189ef0c57..2f49f0bda 100755 --- a/configure +++ b/configure @@ -712,6 +712,7 @@ SSLLIB HAVE_SSL PC_CRYPTO_DEPENDENCY CONFIG_DATE +SOURCE_DATE_EPOCH GCC_DOCKER_LINTFLAGS NETBSD_LINTFLAGS PYUNBOUND_UNINSTALL @@ -959,6 +960,7 @@ SYSTEMD_LIBS SYSTEMD_DAEMON_CFLAGS SYSTEMD_DAEMON_LIBS PYTHON_VERSION +SOURCE_DATE_EPOCH PROTOBUFC_CFLAGS PROTOBUFC_LIBS' @@ -1752,6 +1754,10 @@ Some influential environment variables: The installed Python version to use, for example '2.3'. This string will be appended to the Python interpreter canonical name. + SOURCE_DATE_EPOCH + If it is set, it uses the value of that variable instead of the + current time as the build timestamp. The format is a unix + timestamp. This enables reproducible build output. PROTOBUFC_CFLAGS C compiler flags for PROTOBUFC, overriding pkg-config PROTOBUFC_LIBS @@ -19915,6 +19921,7 @@ printf "%s\n" "$CONFIG_DATE" >&6; } + # Checks for libraries. # libnss diff --git a/configure.ac b/configure.ac index 404cc98ef..07d211e6f 100644 --- a/configure.ac +++ b/configure.ac @@ -910,6 +910,7 @@ if test "`uname`" = "Linux"; then AC_SUBST(GCC_DOCKER_LINTFLAGS) fi AX_BUILD_DATE_EPOCH(CONFIG_DATE, [%Y%m%d]) +AC_ARG_VAR(SOURCE_DATE_EPOCH, [If it is set, it uses the value of that variable instead of the current time as the build timestamp. The format is a unix timestamp. This enables reproducible build output.]) AC_SUBST(CONFIG_DATE) # Checks for libraries. diff --git a/doc/Changelog b/doc/Changelog index 4c0fb1711..d2ec41590 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +28 February 2025: Wouter + - Merge #1238: Prefer SOURCE_DATE_EPOCH over actual time. + Add --help output description for the SOURCE_DATE_EPOCH variable. + 25 February 2025: Wouter - Merge #1243: Do not shadow tm on line 236. From 71bb60e586404d5eab4b79f81d064c0bdd0a9e6e Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Sun, 16 Mar 2025 09:01:03 +0100 Subject: [PATCH 058/218] - Fix 'unbound-control flush_negative' when reporting removed data; reported by David 'eqvinox' Lamparter. --- daemon/remote.c | 14 +++++++------ doc/Changelog | 4 ++++ .../09-unbound-control.conf | 1 + .../09-unbound-control.test | 12 +++++++++++ .../09-unbound-control.testns | 21 ++++++++++++++++--- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index abba0f405..3d683afd1 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -1950,7 +1950,7 @@ bogus_del_rrset(struct lruhash_entry* e, void* arg) /* entry is locked */ struct del_info* inf = (struct del_info*)arg; struct packed_rrset_data* d = (struct packed_rrset_data*)e->data; - if(d->security == sec_status_bogus) { + if(d->security == sec_status_bogus && d->ttl > inf->expired) { d->ttl = inf->expired; inf->num_rrsets++; } @@ -1963,7 +1963,7 @@ bogus_del_msg(struct lruhash_entry* e, void* arg) /* entry is locked */ struct del_info* inf = (struct del_info*)arg; struct reply_info* d = (struct reply_info*)e->data; - if(d->security == sec_status_bogus) { + if(d->security == sec_status_bogus && d->ttl > inf->expired) { d->ttl = inf->expired; d->prefetch_ttl = inf->expired; d->serve_expired_ttl = inf->expired; @@ -1983,7 +1983,7 @@ bogus_del_kcache(struct lruhash_entry* e, void* arg) /* entry is locked */ struct del_info* inf = (struct del_info*)arg; struct key_entry_data* d = (struct key_entry_data*)e->data; - if(d->isbad) { + if(d->isbad && d->ttl > inf->expired) { d->ttl = inf->expired; inf->num_keys++; } @@ -2032,7 +2032,8 @@ negative_del_rrset(struct lruhash_entry* e, void* arg) /* delete the parentside negative cache rrsets, * these are nameserver rrsets that failed lookup, rdata empty */ if((k->rk.flags & PACKED_RRSET_PARENT_SIDE) && d->count == 1 && - d->rrsig_count == 0 && d->rr_len[0] == 0) { + d->rrsig_count == 0 && d->rr_len[0] == 0 && + d->ttl > inf->expired) { d->ttl = inf->expired; inf->num_rrsets++; } @@ -2047,7 +2048,8 @@ negative_del_msg(struct lruhash_entry* e, void* arg) struct reply_info* d = (struct reply_info*)e->data; /* rcode not NOERROR: NXDOMAIN, SERVFAIL, ..: an nxdomain or error * or NOERROR rcode with ANCOUNT==0: a NODATA answer */ - if(FLAGS_GET_RCODE(d->flags) != 0 || d->an_numrrsets == 0) { + if((FLAGS_GET_RCODE(d->flags) != 0 || d->an_numrrsets == 0) && + d->ttl > inf->expired) { d->ttl = inf->expired; d->prefetch_ttl = inf->expired; d->serve_expired_ttl = inf->expired; @@ -2069,7 +2071,7 @@ negative_del_kcache(struct lruhash_entry* e, void* arg) struct key_entry_data* d = (struct key_entry_data*)e->data; /* could be bad because of lookup failure on the DS, DNSKEY, which * was nxdomain or servfail, and thus a result of negative lookups */ - if(d->isbad) { + if(d->isbad && d->ttl > inf->expired) { d->ttl = inf->expired; inf->num_keys++; } diff --git a/doc/Changelog b/doc/Changelog index d2ec41590..bff18592a 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +16 March 2025: Yorgos + - Fix 'unbound-control flush_negative' when reporting removed data; + reported by David 'eqvinox' Lamparter. + 28 February 2025: Wouter - Merge #1238: Prefer SOURCE_DATE_EPOCH over actual time. Add --help output description for the SOURCE_DATE_EPOCH variable. diff --git a/testdata/09-unbound-control.tdir/09-unbound-control.conf b/testdata/09-unbound-control.tdir/09-unbound-control.conf index 719e92309..be65336ea 100644 --- a/testdata/09-unbound-control.tdir/09-unbound-control.conf +++ b/testdata/09-unbound-control.tdir/09-unbound-control.conf @@ -13,6 +13,7 @@ server: msg-cache-size: 4m rrset-cache-size: 4m minimal-responses: yes + trust-anchor: "always.empty. 3600 IN DS 50602 8 2 FA8EE175C47325F4BD46D8A4083C3EBEB11C977D689069F2B41F1A29 B22446B1" # This is nonsense, just to kick the validator view: name: testview view-first: yes # Allow falling back to global local data diff --git a/testdata/09-unbound-control.tdir/09-unbound-control.test b/testdata/09-unbound-control.tdir/09-unbound-control.test index 8bd2220f3..80f64c978 100644 --- a/testdata/09-unbound-control.tdir/09-unbound-control.test +++ b/testdata/09-unbound-control.tdir/09-unbound-control.test @@ -249,6 +249,18 @@ expect_exit_value 1 teststep "clean reload" clean_reload +teststep "Check negative flushing" +query always.empty. +expect_answer "SERVFAIL" +query always.empty. DNSKEY +expect_answer "SERVFAIL" +control_command -c ub.conf flush_negative +expect_exit_value 0 +expect_answer "^ok removed .*, 2 messages and 1 key" +control_command -c ub.conf flush_negative +expect_exit_value 0 +expect_answer "^ok removed .*, 0 messages and 0 key" + teststep "create a new local zone" control_command -c ub.conf local_zone example.net static expect_exit_value 0 diff --git a/testdata/09-unbound-control.tdir/09-unbound-control.testns b/testdata/09-unbound-control.tdir/09-unbound-control.testns index 9a5192fab..44466b4da 100644 --- a/testdata/09-unbound-control.tdir/09-unbound-control.testns +++ b/testdata/09-unbound-control.tdir/09-unbound-control.testns @@ -1,5 +1,4 @@ ; nameserver test file -$ORIGIN example.com. $TTL 3600 ENTRY_BEGIN @@ -7,9 +6,9 @@ MATCH opcode qtype qname REPLY QR AA NOERROR ADJUST copy_id SECTION QUESTION -www IN A +www.example.com. IN A SECTION ANSWER -www IN A 10.20.30.40 +www.example.com. IN A 10.20.30.40 ENTRY_END ENTRY_BEGIN @@ -19,3 +18,19 @@ ADJUST copy_id SECTION QUESTION www.example.net. IN A ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NOERROR +ADJUST copy_id +SECTION QUESTION +always.empty. IN A +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NOERROR +ADJUST copy_id +SECTION QUESTION +always.empty. IN DNSKEY +ENTRY_END From 13afde2cadf62d8ce8f53692d5e378569d284db7 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Mon, 17 Mar 2025 16:38:17 +0100 Subject: [PATCH 059/218] - Fix representation of types GPOS and RESINFO, add rdf type for --- doc/Changelog | 4 ++++ sldns/rrdef.c | 4 ++-- sldns/rrdef.h | 3 +++ sldns/str2wire.c | 10 +++++++++- sldns/str2wire.h | 9 +++++++++ sldns/wire2str.c | 21 +++++++++++++++++++++ sldns/wire2str.h | 13 +++++++++++++ testdata/test_ldnsrr.c3 | 4 ++-- 8 files changed, 63 insertions(+), 5 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index bff18592a..912faef12 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +17 March 2025: Wouter + - Fix representation of types GPOS and RESINFO, add rdf type for + unquoted str. + 16 March 2025: Yorgos - Fix 'unbound-control flush_negative' when reporting removed data; reported by David 'eqvinox' Lamparter. diff --git a/sldns/rrdef.c b/sldns/rrdef.c index 9b4d50f44..2ccb875d4 100644 --- a/sldns/rrdef.c +++ b/sldns/rrdef.c @@ -86,7 +86,7 @@ static const sldns_rdf_type type_px_wireformat[] = { LDNS_RDF_TYPE_INT16, LDNS_RDF_TYPE_DNAME, LDNS_RDF_TYPE_DNAME }; static const sldns_rdf_type type_gpos_wireformat[] = { - LDNS_RDF_TYPE_STR, LDNS_RDF_TYPE_STR, LDNS_RDF_TYPE_STR + LDNS_RDF_TYPE_UNQUOTED, LDNS_RDF_TYPE_UNQUOTED, LDNS_RDF_TYPE_UNQUOTED }; static const sldns_rdf_type type_aaaa_wireformat[] = { LDNS_RDF_TYPE_AAAA }; static const sldns_rdf_type type_loc_wireformat[] = { LDNS_RDF_TYPE_LOC }; @@ -621,7 +621,7 @@ static sldns_rr_descriptor rdata_field_descriptors[] = { {(enum sldns_enum_rr_type)0, "TYPE260", 1, 1, type_0_wireformat, LDNS_RDF_TYPE_NONE, LDNS_RR_NO_COMPRESS, 0 }, /* 261 */ - {LDNS_RR_TYPE_RESINFO, "RESINFO", 1, 0, NULL, LDNS_RDF_TYPE_STR, LDNS_RR_NO_COMPRESS, 0 }, + {LDNS_RR_TYPE_RESINFO, "RESINFO", 1, 0, NULL, LDNS_RDF_TYPE_UNQUOTED, LDNS_RR_NO_COMPRESS, 0 }, /* split in array, no longer contiguous */ diff --git a/sldns/rrdef.h b/sldns/rrdef.h index 5c6074819..24abec622 100644 --- a/sldns/rrdef.h +++ b/sldns/rrdef.h @@ -343,6 +343,9 @@ enum sldns_enum_rdf_type /** 8 * 8 bit hex numbers separated by dashes. For EUI64. */ LDNS_RDF_TYPE_EUI64, + /** Character string without quotes. */ + LDNS_RDF_TYPE_UNQUOTED, + /** A non-zero sequence of US-ASCII letters and numbers in lower case. * For CAA. */ diff --git a/sldns/str2wire.c b/sldns/str2wire.c index b3f5e9798..becd6d385 100644 --- a/sldns/str2wire.c +++ b/sldns/str2wire.c @@ -365,7 +365,8 @@ static int sldns_rdf_type_maybe_quoted(sldns_rdf_type rdf_type) { return rdf_type == LDNS_RDF_TYPE_STR || - rdf_type == LDNS_RDF_TYPE_LONG_STR; + rdf_type == LDNS_RDF_TYPE_LONG_STR || + rdf_type == LDNS_RDF_TYPE_UNQUOTED; } /** see if rdata is quoted */ @@ -1719,6 +1720,8 @@ int sldns_str2wire_rdf_buf(const char* str, uint8_t* rd, size_t* len, return sldns_str2wire_eui48_buf(str, rd, len); case LDNS_RDF_TYPE_EUI64: return sldns_str2wire_eui64_buf(str, rd, len); + case LDNS_RDF_TYPE_UNQUOTED: + return sldns_str2wire_unquoted_buf(str, rd, len); case LDNS_RDF_TYPE_TAG: return sldns_str2wire_tag_buf(str, rd, len); case LDNS_RDF_TYPE_LONG_STR: @@ -2776,6 +2779,11 @@ int sldns_str2wire_eui64_buf(const char* str, uint8_t* rd, size_t* len) return LDNS_WIREPARSE_ERR_OK; } +int sldns_str2wire_unquoted_buf(const char* str, uint8_t* rd, size_t* len) +{ + return sldns_str2wire_str_buf(str, rd, len); +} + int sldns_str2wire_tag_buf(const char* str, uint8_t* rd, size_t* len) { size_t slen = strlen(str); diff --git a/sldns/str2wire.h b/sldns/str2wire.h index 5e4d146d3..db6528a2f 100644 --- a/sldns/str2wire.h +++ b/sldns/str2wire.h @@ -551,6 +551,15 @@ int sldns_str2wire_eui48_buf(const char* str, uint8_t* rd, size_t* len); */ int sldns_str2wire_eui64_buf(const char* str, uint8_t* rd, size_t* len); +/** + * Convert rdf of type LDNS_RDF_TYPE_UNQUOTED from string to wireformat. + * @param str: the text to convert for this rdata element. + * @param rd: rdata buffer for the wireformat. + * @param len: length of rd buffer on input, used length on output. + * @return 0 on success, error on failure. + */ +int sldns_str2wire_unquoted_buf(const char* str, uint8_t* rd, size_t* len); + /** * Convert rdf of type LDNS_RDF_TYPE_TAG from string to wireformat. * @param str: the text to convert for this rdata element. diff --git a/sldns/wire2str.c b/sldns/wire2str.c index 29177b535..f43b47168 100644 --- a/sldns/wire2str.c +++ b/sldns/wire2str.c @@ -1344,6 +1344,8 @@ int sldns_wire2str_rdf_scan(uint8_t** d, size_t* dlen, char** s, size_t* slen, return sldns_wire2str_eui48_scan(d, dlen, s, slen); case LDNS_RDF_TYPE_EUI64: return sldns_wire2str_eui64_scan(d, dlen, s, slen); + case LDNS_RDF_TYPE_UNQUOTED: + return sldns_wire2str_unquoted_scan(d, dlen, s, slen); case LDNS_RDF_TYPE_TAG: return sldns_wire2str_tag_scan(d, dlen, s, slen); case LDNS_RDF_TYPE_LONG_STR: @@ -2047,6 +2049,25 @@ int sldns_wire2str_eui64_scan(uint8_t** d, size_t* dl, char** s, size_t* sl) return w; } +int sldns_wire2str_unquoted_scan(uint8_t** d, size_t* dl, char** s, size_t* sl) +{ + int w = 0; + size_t i, len; + if(*dl < 1) return -1; + len = **d; + if(*dl < 1+len) return -1; + (*d)++; + (*dl)--; + for(i=0; i Date: Tue, 18 Mar 2025 13:59:53 +0100 Subject: [PATCH 060/218] - Fix #1251: WSAPoll first argument cannot be NULL. --- doc/Changelog | 3 +++ util/netevent.c | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 912faef12..697f0273d 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +18 March 2025: Wouter + - Fix #1251: WSAPoll first argument cannot be NULL. + 17 March 2025: Wouter - Fix representation of types GPOS and RESINFO, add rdf type for unquoted str. diff --git a/util/netevent.c b/util/netevent.c index 8f64278c3..998775803 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -442,7 +442,11 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet, int pret; memset(&p, 0, sizeof(p)); p.fd = c->fd; - p.events = POLLOUT | POLLERR | POLLHUP; + p.events = POLLOUT | POLLERR +#ifndef USE_WINSOCK + | POLLHUP +#endif + ; # ifndef USE_WINSOCK pret = poll(&p, 1, SEND_BLOCKED_WAIT_TIMEOUT); # else @@ -496,7 +500,8 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet, #ifndef USE_WINSOCK pret = poll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1)); #else - pret = WSAPoll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1)); + Sleep((SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1)); + pret = 0; #endif if(pret < 0 && #ifndef USE_WINSOCK @@ -751,7 +756,11 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet, int pret; memset(&p, 0, sizeof(p)); p.fd = c->fd; - p.events = POLLOUT | POLLERR | POLLHUP; + p.events = POLLOUT | POLLERR +#ifndef USE_WINSOCK + | POLLHUP +#endif + ; # ifndef USE_WINSOCK pret = poll(&p, 1, SEND_BLOCKED_WAIT_TIMEOUT); # else @@ -805,7 +814,8 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet, #ifndef USE_WINSOCK pret = poll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1)); #else - pret = WSAPoll(NULL, 0, (SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1)); + Sleep((SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1)); + pret = 0; #endif if(pret < 0 && #ifndef USE_WINSOCK From 30b9cb5f813003d0a2b1c2e678652396615b1b7d Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 18 Mar 2025 14:01:53 +0100 Subject: [PATCH 061/218] - Fix for windows compile create ssl contexts. --- doc/Changelog | 1 + winrc/win_svc.c | 32 ++++++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 697f0273d..cccddf8fa 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,5 +1,6 @@ 18 March 2025: Wouter - Fix #1251: WSAPoll first argument cannot be NULL. + - Fix for windows compile create ssl contexts. 17 March 2025: Wouter - Fix representation of types GPOS and RESINFO, add rdf type for diff --git a/winrc/win_svc.c b/winrc/win_svc.c index bd28bd206..40e12f1cf 100644 --- a/winrc/win_svc.c +++ b/winrc/win_svc.c @@ -363,16 +363,36 @@ service_init(int r, struct daemon** d, struct config_file** c) return 0; } if(cfg->ssl_service_key && cfg->ssl_service_key[0]) { - if(!(daemon->listen_sslctx = listen_sslctx_create( - cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) + if(!(daemon->listen_dot_sslctx = listen_sslctx_create( + cfg->ssl_service_key, cfg->ssl_service_pem, NULL, + cfg->tls_ciphers, cfg->tls_ciphersuites, + (cfg->tls_session_ticket_keys.first && + cfg->tls_session_ticket_keys.first->str[0] != 0), + 1, 0))) { fatal_exit("could not set up listen SSL_CTX"); + } +#ifdef HAVE_NGHTTP2_NGHTTP2_H + if(cfg_has_https(cfg)) { + if(!(daemon->listen_doh_sslctx = listen_sslctx_create( + cfg->ssl_service_key, cfg->ssl_service_pem, NULL, + cfg->tls_ciphers, cfg->tls_ciphersuites, + (cfg->tls_session_ticket_keys.first && + cfg->tls_session_ticket_keys.first->str[0] != 0), + 0, 1))) { + fatal_exit("could not set up listen doh SSL_CTX"); + } + } +#endif #ifdef HAVE_NGTCP2 - if(!(daemon->quic_sslctx = quic_sslctx_create( - cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) - fatal_exit("could not set up quic SSL_CTX"); + if(cfg_has_quic(cfg)) { + if(!(daemon->listen_quic_sslctx = quic_sslctx_create( + cfg->ssl_service_key, cfg->ssl_service_pem, NULL))) { + fatal_exit("could not set up quic SSL_CTX"); + } + } #endif /* HAVE_NGTCP2 */ } - if(!(daemon->connect_sslctx = connect_sslctx_create(NULL, NULL, + if(!(daemon->connect_dot_sslctx = connect_sslctx_create(NULL, NULL, cfg->tls_cert_bundle, cfg->tls_win_cert))) fatal_exit("could not set up connect SSL_CTX"); From eccf52e39d73c49267ebbcb228eac9715568e7f8 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 20 Mar 2025 15:51:03 +0100 Subject: [PATCH 062/218] - Fix print of RR type NSAP-PTR, it is an unquoted string. --- doc/Changelog | 3 +++ sldns/rrdef.c | 2 +- testdata/test_ldnsrr.5 | 2 ++ testdata/test_ldnsrr.c5 | 4 ++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index cccddf8fa..f6777a536 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +20 March 2025: Wouter + - Fix print of RR type NSAP-PTR, it is an unquoted string. + 18 March 2025: Wouter - Fix #1251: WSAPoll first argument cannot be NULL. - Fix for windows compile create ssl contexts. diff --git a/sldns/rrdef.c b/sldns/rrdef.c index 2ccb875d4..9e2100d53 100644 --- a/sldns/rrdef.c +++ b/sldns/rrdef.c @@ -72,7 +72,7 @@ static const sldns_rdf_type type_nsap_wireformat[] = { LDNS_RDF_TYPE_NSAP }; static const sldns_rdf_type type_nsap_ptr_wireformat[] = { - LDNS_RDF_TYPE_STR + LDNS_RDF_TYPE_UNQUOTED }; static const sldns_rdf_type type_sig_wireformat[] = { LDNS_RDF_TYPE_TYPE, LDNS_RDF_TYPE_ALG, LDNS_RDF_TYPE_INT8, LDNS_RDF_TYPE_INT32, diff --git a/testdata/test_ldnsrr.5 b/testdata/test_ldnsrr.5 index 2762ca82d..1f2837a67 100644 --- a/testdata/test_ldnsrr.5 +++ b/testdata/test_ldnsrr.5 @@ -174,3 +174,5 @@ root-servers.net. 3600000 IN ZONEMD 2018091100 1 1 ( f1ca0ccd91bd5573d9f43 foo. 12345 IN LOC 12 45 52.333 N 105 40 33.452 W -24m 0.1m 0.1m 0.1m ; from ldns issue #147, fix #148, tab between quoted strings. foo 12345 IN HINFO "hohum" "weirdo" +0.0.3.6.1.0.0.0.f.f.f.f.f.f.3.3.1.e.1.0.0.0.0.0.0.0.0.0.a.5.0.0.0.8.5.0.0.0.7.4.NSAP.INT. IN NSAP-PTR foo.bar.com. +0.0.4.6.1.0.0.0.f.f.f.f.f.f.3.3.1.e.1.0.0.0.0.0.0.0.0.0.a.5.0.0.0.8.5.0.0.0.7.4.NSAP.INT. IN NSAP-PTR host.school.de. diff --git a/testdata/test_ldnsrr.c5 b/testdata/test_ldnsrr.c5 index e86532c34..72d7b69d6 100644 --- a/testdata/test_ldnsrr.c5 +++ b/testdata/test_ldnsrr.c5 @@ -214,3 +214,7 @@ root-servers.net. 3600000 IN ZONEMD 2018091100 1 1 F1CA0CCD91BD5573D9F431C00EE01 foo. 12345 IN LOC 12 45 52.333 N 105 40 33.452 W -24m 0.10m 0.10m 0.10m 03666F6F00000D000100003039000D05686F68756D0677656972646F foo. 12345 IN HINFO "hohum" "weirdo" +0130013001330136013101300130013001660166016601660166016601330133013101650131013001300130013001300130013001300130016101350130013001300138013501300130013001370134044E53415003494E54000017000100000E10000D0C666F6F2E6261722E636F6D2E +0.0.3.6.1.0.0.0.f.f.f.f.f.f.3.3.1.e.1.0.0.0.0.0.0.0.0.0.a.5.0.0.0.8.5.0.0.0.7.4.NSAP.INT. 3600 IN NSAP-PTR foo.bar.com. +0130013001340136013101300130013001660166016601660166016601330133013101650131013001300130013001300130013001300130016101350130013001300138013501300130013001370134044E53415003494E54000017000100000E1000100F686F73742E7363686F6F6C2E64652E +0.0.4.6.1.0.0.0.f.f.f.f.f.f.3.3.1.e.1.0.0.0.0.0.0.0.0.0.a.5.0.0.0.8.5.0.0.0.7.4.NSAP.INT. 3600 IN NSAP-PTR host.school.de. From a42fb99508bda49af59828cf44e6416e512b9a20 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 21 Mar 2025 12:56:21 +0100 Subject: [PATCH 063/218] - Fix #1253: Cache entries fail to be removed from Redis cachedb backend with unbound-control flush* +c. --- cachedb/redis.c | 39 ++++++++++++++++++++++++--------------- doc/Changelog | 4 ++++ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/cachedb/redis.c b/cachedb/redis.c index 248a3d7b4..c38bcaa14 100644 --- a/cachedb/redis.c +++ b/cachedb/redis.c @@ -61,7 +61,7 @@ struct redis_moddata { struct timeval command_timeout; /* timeout for commands */ struct timeval connect_timeout; /* timeout for connect */ int logical_db; /* the redis logical database to use */ - int setex_available; /* if the SETEX command is supported */ + int set_with_ex_available; /* if the SET with EX command is supported */ }; static redisReply* redis_command(struct module_env*, struct cachedb_env*, @@ -195,12 +195,12 @@ redis_init(struct module_env* env, struct cachedb_env* cachedb_env) moddata->ctxs[env->alloc->thread_num] != NULL) { redisReply* rep = NULL; int redis_reply_type = 0; - /** check if setex command is supported */ + /** check if set with ex command is supported */ rep = redis_command(env, cachedb_env, - "SETEX __UNBOUND_REDIS_CHECK__ 1 none", NULL, 0); + "SET __UNBOUND_REDIS_CHECK__ none EX 1", NULL, 0); if(!rep) { /** init failed, no response from redis server*/ - goto setex_fail; + goto set_with_ex_fail; } redis_reply_type = rep->type; freeReplyObject(rep); @@ -208,17 +208,17 @@ redis_init(struct module_env* env, struct cachedb_env* cachedb_env) case REDIS_REPLY_STATUS: break; default: - /** init failed, setex command not supported */ - goto setex_fail; + /** init failed, set_with_ex command not supported */ + goto set_with_ex_fail; } - moddata->setex_available = 1; + moddata->set_with_ex_available = 1; } return 1; -setex_fail: +set_with_ex_fail: log_err("redis_init: failure during redis_init, the " - "redis-expire-records option requires the SETEX command " - "(redis >= 2.0.0)"); + "redis-expire-records option requires the SET with EX command " + "(redis >= 2.6.2)"); return 1; fail: moddata_clean(&moddata); @@ -352,12 +352,14 @@ redis_store(struct module_env* env, struct cachedb_env* cachedb_env, int n; struct redis_moddata* moddata = (struct redis_moddata*) cachedb_env->backend_data; - int set_ttl = (moddata->setex_available && + int set_ttl = (moddata->set_with_ex_available && env->cfg->redis_expire_records && (!env->cfg->serve_expired || env->cfg->serve_expired_ttl > 0)); /* Supported commands: * - "SET " + key + " %b" - * - "SETEX " + key + " " + ttl + " %b" + * - "SET " + key + " %b EX " + ttl + * older redis 2.0.0 was "SETEX " + key + " " + ttl + " %b" + * - "EXPIRE " + key + " 0" */ char cmdbuf[6+(CACHEDB_HASHSIZE/8)*2+11+3+1]; @@ -365,14 +367,21 @@ redis_store(struct module_env* env, struct cachedb_env* cachedb_env, verbose(VERB_ALGO, "redis_store %s (%d bytes)", key, (int)data_len); /* build command to set to a binary safe string */ n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b", key); + } else if(ttl == 0) { + /* use the EXPIRE command, SET with EX 0 is an invalid time. */ + verbose(VERB_ALGO, "redis_store expire %s (%d bytes)", + key, (int)data_len); + n = snprintf(cmdbuf, sizeof(cmdbuf), "EXPIRE %s 0", key); + data = NULL; + data_len = 0; } else { /* add expired ttl time to redis ttl to avoid premature eviction of key */ ttl += env->cfg->serve_expired_ttl; verbose(VERB_ALGO, "redis_store %s (%d bytes) with ttl %u", - key, (int)data_len, (uint32_t)ttl); + key, (int)data_len, (unsigned)(uint32_t)ttl); /* build command to set to a binary safe string */ - n = snprintf(cmdbuf, sizeof(cmdbuf), "SETEX %s %u %%b", key, - (uint32_t)ttl); + n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b EX %u", key, + (unsigned)(uint32_t)ttl); } diff --git a/doc/Changelog b/doc/Changelog index f6777a536..7b98f440a 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +21 March 2025: Wouter + - Fix #1253: Cache entries fail to be removed from Redis cachedb + backend with unbound-control flush* +c. + 20 March 2025: Wouter - Fix print of RR type NSAP-PTR, it is an unquoted string. From 0eabc8d0f123f7b9d65cfe46935f04cb10ae1d50 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 21 Mar 2025 14:07:22 +0100 Subject: [PATCH 064/218] - Fix for #1253: Fix for redis cachedb backend to expect an integer reply for the EXPIRE command. --- cachedb/redis.c | 4 +++- doc/Changelog | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cachedb/redis.c b/cachedb/redis.c index c38bcaa14..183879dd0 100644 --- a/cachedb/redis.c +++ b/cachedb/redis.c @@ -369,6 +369,7 @@ redis_store(struct module_env* env, struct cachedb_env* cachedb_env, n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b", key); } else if(ttl == 0) { /* use the EXPIRE command, SET with EX 0 is an invalid time. */ + /* Replies with REDIS_REPLY_INTEGER of 1. */ verbose(VERB_ALGO, "redis_store expire %s (%d bytes)", key, (int)data_len); n = snprintf(cmdbuf, sizeof(cmdbuf), "EXPIRE %s 0", key); @@ -394,7 +395,8 @@ redis_store(struct module_env* env, struct cachedb_env* cachedb_env, if(rep) { verbose(VERB_ALGO, "redis_store set completed"); if(rep->type != REDIS_REPLY_STATUS && - rep->type != REDIS_REPLY_ERROR) { + rep->type != REDIS_REPLY_ERROR && + rep->type != REDIS_REPLY_INTEGER) { log_err("redis_store: unexpected type of reply (%d)", rep->type); } diff --git a/doc/Changelog b/doc/Changelog index 7b98f440a..0400c54cd 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,8 @@ 21 March 2025: Wouter - Fix #1253: Cache entries fail to be removed from Redis cachedb backend with unbound-control flush* +c. + - Fix for #1253: Fix for redis cachedb backend to expect an integer + reply for the EXPIRE command. 20 March 2025: Wouter - Fix print of RR type NSAP-PTR, it is an unquoted string. From 376f2ade2aedc308a9e989f0f1a548abd419e4b3 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Mon, 24 Mar 2025 08:54:54 +0100 Subject: [PATCH 065/218] - Fix #1254: `send failed: Socket is not connected` and `remote address is 0.0.0.0 port 53`. --- doc/Changelog | 4 ++++ util/netevent.c | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 0400c54cd..6e90373aa 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +24 March 2025: Wouter + - Fix #1254: `send failed: Socket is not connected` and + `remote address is 0.0.0.0 port 53`. + 21 March 2025: Wouter - Fix #1253: Cache entries fail to be removed from Redis cachedb backend with unbound-control flush* +c. diff --git a/util/netevent.c b/util/netevent.c index 998775803..5e4095061 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -369,6 +369,15 @@ udp_send_errno_needs_log(struct sockaddr* addr, socklen_t addrlen) (struct sockaddr_storage*)addr, addrlen) && verbosity < VERB_DETAIL) return 0; +# ifdef ENOTCONN + /* For 0.0.0.0, ::0 targets it can return that socket is not connected. + * This can be ignored, and the address skipped. It remains + * possible to send there for completeness in configuration. */ + if(errno == ENOTCONN && addr_is_any( + (struct sockaddr_storage*)addr, addrlen) && + verbosity < VERB_DETAIL) + return 0; +# endif return 1; } From d03e4b1884a6a75169db56d16c13ca1be8a808b6 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 26 Mar 2025 14:58:54 +0100 Subject: [PATCH 066/218] - Fix #1255: Multiple pinnings to vulnerable copies of libexpat. --- .github/workflows/analysis_ports.yml | 9 ++++++--- contrib/android/install_expat.sh | 11 +++++++---- contrib/ios/install_expat.sh | 11 +++++++---- doc/Changelog | 3 +++ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/.github/workflows/analysis_ports.yml b/.github/workflows/analysis_ports.yml index 85b723912..f843eeb5e 100644 --- a/.github/workflows/analysis_ports.yml +++ b/.github/workflows/analysis_ports.yml @@ -196,6 +196,9 @@ jobs: persist-credentials: false - name: test_windows if: ${{ matrix.test_windows == 'yes' }} + env: + LIBEXPAT_FNAME: expat-2.7.0 + LIBEXPAT_VERSION_DIR: R_2_7_0 shell: bash run: | export unboundpath=`pwd` @@ -238,9 +241,9 @@ jobs: cd .. mkdir expat echo "curl expat" - curl -L -k -s -S -o expat-2.2.10.tar.gz https://github.com/libexpat/libexpat/releases/download/R_2_2_10/expat-2.2.10.tar.gz - tar xzf expat-2.2.10.tar.gz - cd expat-2.2.10 + curl -L -k -s -S -o $LIBEXPAT_FNAME.tar.gz https://github.com/libexpat/libexpat/releases/download/$LIBEXPAT_VERSION_DIR/$LIBEXPAT_FNAME.tar.gz + tar xzf $LIBEXPAT_FNAME.tar.gz + cd $LIBEXPAT_FNAME echo "./configure SHELL=/usr/bin/bash CONFIG_SHELL=/usr/bin/bash --prefix=\"$prepath/expat\" --exec-prefix=\"$prepath/expat\" --bindir=\"$prepath/expat/bin\" --includedir=\"$prepath/expat/include\" --mandir=\"$prepath/expat/man\" --libdir=\"$prepath/expat/lib\"" ./configure SHELL=/usr/bin/bash CONFIG_SHELL=/usr/bin/bash --prefix="$prepath/expat" --exec-prefix="$prepath/expat" --bindir="$prepath/expat/bin" --includedir="$prepath/expat/include" --mandir="$prepath/expat/man" --libdir="$prepath/expat/lib" # fixup SHELL is treated specially, but SHELZZ is not by make. diff --git a/contrib/android/install_expat.sh b/contrib/android/install_expat.sh index ffb22322c..5d736b8b9 100755 --- a/contrib/android/install_expat.sh +++ b/contrib/android/install_expat.sh @@ -1,21 +1,24 @@ #!/usr/bin/env bash +LIBEXPAT_FNAME=expat-2.7.0 +LIBEXPAT_VERSION_DIR=R_2_7_0 + echo "Downloading Expat" -if ! curl -L -k -s -o expat-2.2.9.tar.gz https://github.com/libexpat/libexpat/releases/download/R_2_2_9/expat-2.2.9.tar.gz; +if ! curl -L -k -s -o $LIBEXPAT_FNAME.tar.gz https://github.com/libexpat/libexpat/releases/download/$LIBEXPAT_VERSION_DIR/$LIBEXPAT_FNAME.tar.gz; then echo "Failed to download Expat" exit 1 fi echo "Unpacking Expat" -rm -rf ./expat-2.2.9 -if ! tar -xf expat-2.2.9.tar.gz; +rm -rf ./$LIBEXPAT_FNAME +if ! tar -xf $LIBEXPAT_FNAME.tar.gz; then echo "Failed to unpack Expat" exit 1 fi -cd expat-2.2.9 || exit 1 +cd $LIBEXPAT_FNAME || exit 1 echo "Configuring Expat" if ! ./configure --build="$AUTOTOOLS_BUILD" --host="$AUTOTOOLS_HOST" --prefix="$ANDROID_PREFIX"; then diff --git a/contrib/ios/install_expat.sh b/contrib/ios/install_expat.sh index 9471b5aff..b8e3a9046 100755 --- a/contrib/ios/install_expat.sh +++ b/contrib/ios/install_expat.sh @@ -1,21 +1,24 @@ #!/usr/bin/env bash +LIBEXPAT_FNAME=expat-2.7.0 +LIBEXPAT_VERSION_DIR=R_2_7_0 + echo "Downloading Expat" -if ! curl -L -k -s -o expat-2.2.9.tar.gz https://github.com/libexpat/libexpat/releases/download/R_2_2_9/expat-2.2.9.tar.gz; +if ! curl -L -k -s -o $LIBEXPAT_FNAME.tar.gz https://github.com/libexpat/libexpat/releases/download/$LIBEXPAT_VERSION_DIR/$LIBEXPAT_FNAME.tar.gz; then echo "Failed to download Expat" exit 1 fi echo "Unpacking Expat" -rm -rf ./expat-2.2.9 -if ! tar -xf expat-2.2.9.tar.gz; +rm -rf ./$LIBEXPAT_FNAME +if ! tar -xf $LIBEXPAT_FNAME.tar.gz; then echo "Failed to unpack Expat" exit 1 fi -cd expat-2.2.9 || exit 1 +cd $LIBEXPAT_FNAME || exit 1 export PKG_CONFIG_PATH="$IOS_PREFIX/lib/pkgconfig" diff --git a/doc/Changelog b/doc/Changelog index 6e90373aa..3a8a4024b 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +26 March 2025: Yorgos + - Fix #1255: Multiple pinnings to vulnerable copies of libexpat. + 24 March 2025: Wouter - Fix #1254: `send failed: Socket is not connected` and `remote address is 0.0.0.0 port 53`. From d91c857e22193da25240675f037f62359af7cdde Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 26 Mar 2025 16:15:16 +0100 Subject: [PATCH 067/218] - For #1255, for ios use an older expat version that does not require C++11 language features. --- contrib/ios/install_expat.sh | 4 ++-- doc/Changelog | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/contrib/ios/install_expat.sh b/contrib/ios/install_expat.sh index b8e3a9046..3219558df 100755 --- a/contrib/ios/install_expat.sh +++ b/contrib/ios/install_expat.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -LIBEXPAT_FNAME=expat-2.7.0 -LIBEXPAT_VERSION_DIR=R_2_7_0 +LIBEXPAT_FNAME=expat-2.6.3 +LIBEXPAT_VERSION_DIR=R_2_6_3 echo "Downloading Expat" if ! curl -L -k -s -o $LIBEXPAT_FNAME.tar.gz https://github.com/libexpat/libexpat/releases/download/$LIBEXPAT_VERSION_DIR/$LIBEXPAT_FNAME.tar.gz; diff --git a/doc/Changelog b/doc/Changelog index 3a8a4024b..d90263a36 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,5 +1,7 @@ 26 March 2025: Yorgos - Fix #1255: Multiple pinnings to vulnerable copies of libexpat. + - For #1255, for ios use an older expat version that does not require + C++11 language features. 24 March 2025: Wouter - Fix #1254: `send failed: Socket is not connected` and From 94a84d3387615e17c51efbc0a8356ba641322ede Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 26 Mar 2025 16:40:10 +0100 Subject: [PATCH 068/218] - For #1255, for ios disable building tests that require C++11. --- contrib/ios/install_expat.sh | 7 ++++--- doc/Changelog | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/contrib/ios/install_expat.sh b/contrib/ios/install_expat.sh index 3219558df..915279f40 100755 --- a/contrib/ios/install_expat.sh +++ b/contrib/ios/install_expat.sh @@ -23,9 +23,10 @@ cd $LIBEXPAT_FNAME || exit 1 export PKG_CONFIG_PATH="$IOS_PREFIX/lib/pkgconfig" echo "Configuring Expat" -if ! ./configure \ - --build="$AUTOTOOLS_BUILD" --host="$AUTOTOOLS_HOST" \ - --prefix="$IOS_PREFIX" ; then +if ! ./configure --without-tests \ + --build="$AUTOTOOLS_BUILD" --host="$AUTOTOOLS_HOST" \ + --prefix="$IOS_PREFIX" ; +then echo "Error: Failed to configure Expat" cat config.log exit 1 diff --git a/doc/Changelog b/doc/Changelog index d90263a36..75257de94 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,7 @@ - Fix #1255: Multiple pinnings to vulnerable copies of libexpat. - For #1255, for ios use an older expat version that does not require C++11 language features. + - For #1255, for ios disable building tests that require C++11. 24 March 2025: Wouter - Fix #1254: `send failed: Socket is not connected` and From 23273d76a5cc21904d185ca1b195076d3d5082a3 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 26 Mar 2025 16:51:46 +0100 Subject: [PATCH 069/218] - For #1255, for ios try the latest expat version again. --- contrib/ios/install_expat.sh | 4 ++-- doc/Changelog | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contrib/ios/install_expat.sh b/contrib/ios/install_expat.sh index 915279f40..4972c467b 100755 --- a/contrib/ios/install_expat.sh +++ b/contrib/ios/install_expat.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -LIBEXPAT_FNAME=expat-2.6.3 -LIBEXPAT_VERSION_DIR=R_2_6_3 +LIBEXPAT_FNAME=expat-2.7.0 +LIBEXPAT_VERSION_DIR=R_2_7_0 echo "Downloading Expat" if ! curl -L -k -s -o $LIBEXPAT_FNAME.tar.gz https://github.com/libexpat/libexpat/releases/download/$LIBEXPAT_VERSION_DIR/$LIBEXPAT_FNAME.tar.gz; diff --git a/doc/Changelog b/doc/Changelog index 75257de94..f417fc328 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -3,6 +3,7 @@ - For #1255, for ios use an older expat version that does not require C++11 language features. - For #1255, for ios disable building tests that require C++11. + - For #1255, for ios try the latest expat version again. 24 March 2025: Wouter - Fix #1254: `send failed: Socket is not connected` and From aa77d023364995c21663569c661795c03945f3b9 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 27 Mar 2025 10:47:20 +0100 Subject: [PATCH 070/218] - Fix unit test dname log printout typecast. --- doc/Changelog | 3 +++ testcode/unitdname.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index f417fc328..28adb62f8 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +27 March 2025: Wouter + - Fix unit test dname log printout typecast. + 26 March 2025: Yorgos - Fix #1255: Multiple pinnings to vulnerable copies of libexpat. - For #1255, for ios use an older expat version that does not require diff --git a/testcode/unitdname.c b/testcode/unitdname.c index a3fa0c508..08a2dbad7 100644 --- a/testcode/unitdname.c +++ b/testcode/unitdname.c @@ -998,7 +998,7 @@ dname_test_str(sldns_buffer* buff) unit_assert( pkt_dname_len(buff) == 3); dname_str(sldns_buffer_begin(buff), result); if(strcmp( "?.", result) != 0 ) { - log_err("ASCII value '0x%lX' allowed in string output", i); + log_err("ASCII value '0x%lX' allowed in string output", (unsigned long)i); unit_assert(0); } } From f76365754e6ad9d2bca226dc6fd06310872ad8d3 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 27 Mar 2025 10:52:00 +0100 Subject: [PATCH 071/218] - Fix for ci test, expat is installed on the osx image. --- .github/workflows/analysis_ports.yml | 4 ++-- doc/Changelog | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/analysis_ports.yml b/.github/workflows/analysis_ports.yml index f843eeb5e..d71875076 100644 --- a/.github/workflows/analysis_ports.yml +++ b/.github/workflows/analysis_ports.yml @@ -31,12 +31,12 @@ jobs: clang_analysis: "yes" - name: OS X os: macos-latest - install_expat: "yes" + install_expat: "no" config: "--enable-debug --disable-flto --with-ssl=/opt/homebrew/opt/openssl --with-libexpat=/opt/homebrew/opt/expat" make_test: "yes" - name: Clang on OS X os: macos-latest - install_expat: "yes" + install_expat: "no" config: "CC=clang --enable-debug --disable-flto --with-ssl=/opt/homebrew/opt/openssl --with-libexpat=/opt/homebrew/opt/expat --disable-static" make_test: "yes" clang_analysis: "yes" diff --git a/doc/Changelog b/doc/Changelog index 28adb62f8..78715a023 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,5 +1,6 @@ 27 March 2025: Wouter - Fix unit test dname log printout typecast. + - Fix for ci test, expat is installed on the osx image. 26 March 2025: Yorgos - Fix #1255: Multiple pinnings to vulnerable copies of libexpat. From 914cef75f93429b2fdb06b21114e83f6e97643fb Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Mon, 31 Mar 2025 14:25:16 +0200 Subject: [PATCH 072/218] - iana portlist update. --- doc/Changelog | 3 +++ util/iana_ports.inc | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 78715a023..8a5726fe3 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +31 March 2025: Wouter + - iana portlist update. + 27 March 2025: Wouter - Fix unit test dname log printout typecast. - Fix for ci test, expat is installed on the osx image. diff --git a/util/iana_ports.inc b/util/iana_ports.inc index c39035636..198a47eb1 100644 --- a/util/iana_ports.inc +++ b/util/iana_ports.inc @@ -1,6 +1,4 @@ 1, -2, -3, 5, 7, 9, @@ -2426,6 +2424,7 @@ 2791, 2792, 2793, +2794, 2795, 2796, 2797, @@ -4358,6 +4357,7 @@ 5911, 5912, 5913, +5914, 5963, 5968, 5969, @@ -5477,6 +5477,7 @@ 44900, 45000, 45054, +45185, 45514, 45678, 45825, From 218f5cfc9217a61bf9181e152d16235918b4b010 Mon Sep 17 00:00:00 2001 From: Wouter Wijngaards Date: Mon, 31 Mar 2025 15:25:24 +0200 Subject: [PATCH 073/218] Fast Reload Option (#1042) * - fast-reload, add unbound-control fast_reload * - fast-reload, make a thread to service the unbound-control command. * - fast-reload, communication sockets for information transfer. * - fast-reload, fix compile for unbound-dnstap-socket. * - fast-reload, set nonblocking communication to keep the server thread responding to DNS requests. * - fast-reload, poll routine to test for readiness, timeout fails connection. * - fast-reload, detect loop in sock_poll_timeout routine. * - fast-reload, send done and exited notification. * - fast-reload, defines for constants in ipc. * - fast-reload, ipc socket recv and send resists partial reads and writes and can continue byte by byte. Also it can continue after an interrupt. * - fast-reload, send exit command to thread when done. * - fast-reload, output strings for client on string list. * - fast-reload, add newline to terminal output. * - fast-reload, send client string to remote client. * - fast-reload, better debug output. * - fast-reload, print queue structure, for output to the remote client. * - fast-reload, move print items to print queue from fast_reload_thread struct. * - fast-reload, keep list of pending print queue items in daemon struct. * - fast-reload, comment explains in_list for printq to print remainder. * - fast-reload, unit test testdata/fast_reload_thread.tdir that tests the thread output. * - fast-reload, fix test link for fast_reload_printq_list_delete function. * - fast-reload, reread config file from disk. * - fast-reload, unshare forwards, making the structure locked, with an rwlock. * - fast-reload, for nonthreaded, the unbound-control commands forward, forward_add and forward_delete should be distributed to other processes, but when threaded, they should not be distributed to other threads because the structure is not thread specific any more. * - fast-reload, unshared stub hints, making the structure locked, with an rwlock. * - fast-reload, helpful comments for hints lookup function return value. * - fast-reload, fix bug in fast reload printout, the strlist appendlist routine, and printout time statistics after the reload is done. * - fast-reload, keep track of reloadtime and deletestime and print them. * - fast-reload, keep track of constructtime and print it. * - fast-reload, construct new items. * - fast-reload, better comment. * - fast-reload, reload the config and swap trees for forwards and stub hints. * - fast-reload, in forwards_swap_tree set protection of trees with locks. * - fast-reload, in hints_swap_tree also swap the node count of the trees. * - fast-reload, reload ipc to stop and start threads. * - fast-reload, unused forward declarations removed. * - fast-reload, unit test that fast reload works with forwards and stubs. * - fast-reload, fix clang analyzer warnings. * - fast-reload, small documentation entry in unbound-control -h output. * - fast-reload, printout memory use by fast reload, in bytes. * - fast-reload, compile without threads. * - fast-reload, document fast_reload in man page. * - fast-reload, print ok when done successfully. * - fast-reload, option for fast-reload commandline, +v verbosity option, with timing and memory use output. * - fast-reload, option for fast-reload commandline, +p does not pause threads. * - fast-reload, option for fast-reload commandline, +d drops mesh queries. * - fast-reload, fix to poll every thread with nopause to make certain that resources are not held by the threads and can be deleted. * - fast-reload, fix to use atomic store for config variables with nopause. * - fast-reload, reload views. * - fast-reload, when tag defines are different, it drops the queries. * - fast-reload, fix tag define check. * - fast-reload, document that tag change causes drop of queries. * - fast-reload, fix space in documentation man page. * - fast-reload, copy respip client information to query state, put views tree in module env for lookup. * - fast-reload, nicer respip view comparison. * - fast-reload, respip global set is in module env. * - fast-reload, document that respip_client_info acl info is copied. * - fast-reload, reload the respip_set. * - fast-reload, document no pause and pick up of use_response_ip boolean. * - fast-reload, fix test compile. * - fast-reload, reload local zones. * Update locking management for iter_fwd and iter_hints methods. (#1054) fast reload, move most of the locking management to iter_fwd and iter_hints methods. The caller still has the ability to handle its own locking, if desired, for atomic operations on sets of different structs. Co-authored-by: Wouter Wijngaards * - fast-reload, reload access-control. * - fast-reload, reload access control interface, such as interface-action. * - fast-reload, reload tcp-connection-limit. * - fast-reload, improve comments on acl_list and tcl_list swap tree. * - fast-reload, fixup references to old tcp connection limits in open tcp connections. * - fast-reload, fixup to clean tcp connection also for different linked order. * - fast-reload, if no tcp connection limits existed, no need to remove references for that. * - fast-reload, document more options that work and do not work. * - fast-reload, reload auth_zone and rpz data. * - fast-reload, fix auth_zones_get_mem. * - fast-reload, fix compilation of testbound for the new comm_timer_get_mem reference in remote control. * - fast-reload, change use_rpz with reload. * - fast-reload, list changes in auth zones and stop zonemd callbacks for deleted auth zones. * - fast-reload, note xtree is not swapped, and why it is not swapped. * - fast-reload, for added auth zones, pick up zone transfer and zonemd tasks. * - fast-reload, unlock xfr when done with transfer pick up. * - fast-reload, unlock z when picking up the xfr for it during transfer task pick up. * - fast-reload, pick up task changes for added, deleted and modified auth zones. * - fast-reload, remove xfr of auth zone deletion without tasks. * - fast-reload, pick up zone transfer config. * - fast-reload, the main worker thread picks up the transfer tasks and also performs setup of the xfer struct. * - fast-reload, keep writelock on newzone when auth zone changes. * - fast-reload, change cachedb_enabled setting. * - fast-reload, pick up edns-strings config. * - fast-reload, note that settings are not updated. * - fast-reload, pick up dnstap config. * - fast-reload, dnstap options that need to be loaded without +p. * - fast-reload, fix auth zone reload * - fast-reload, remove debug for auth zone test. * - fast-reload, fix auth zone reload with zone transfer. * - fast-reload, fix auth zone reload lock order. * - fast-reload, remove debug from fast reload test. * - fast-reload, remove unused function. * - fast-reload, fix the worker trust anchor probe timer lock acquisition in the probe answer callback routine for trust anchor probes. * - fast-reload, reload trust anchors. * - fast-reload, fix trust anchor reload lock on autr global data and test for trust anchor reload. * - fast-reload, adjust cache sizes. * - fast-reload, reload cache sizes when changed. * - fast-reload, reload validator env changes. * - fast-reload, reload mesh changes. * - fast-reload, check for incompatible changes. * - fast-reload, improve error text for incompatible change. * - fast-reload, fix check config option compatibility. * - fast-reload, improve error text for nopause change. * - fast-reload, fix spelling of incompatible options. * - fast-reload, reload target-fetch-policy, outbound-msg-retry, max-sent-count and max-query-restarts. * - fast-reload, check nopause config change for target-fetch-policy. * - fast-reload, reload do-not-query-address, private-address and capt-exempt. * - fast-reload, check nopause config change for do-not-query-address, private-address and capt-exempt. * - fast-reload, check fast reload not possible due to interface and outgoing-interface changes. * - fast-reload, reload nat64 settings. * - fast-reload, reload settings stored in the infra structure. * - fast-reload, fix modstack lookup and remove outgoing-range check. * - fast-reload, more explanation for config parse failure. * - fast-reload, reload worker outside network changes. * - fast-reload, detect incompatible changes in network settings. * fast-reload, commit test files. * - fast-reload, fix warnings for call types in windows compile. * - fast-reload, fix warnings and comm_point_internal for tcp wouldblock calls. * - fast-reload, extend lock checks for repeat thread ids. * - fast-reload, additional test cases, cache change and tag changes. * - fast-reload, fix documentation for auth_zone_verify_zonemd_with_key. * - fast-reload, fix copy_cfg type casts and memory leak on config parse failure. * - fast-reload, fix use of WSAPoll. * Review comments for the fast reload feature (#1259) * - fast-reload review, respip set can be null from a view. * - fast-reload review, typos. * - fast-reload review, keep clang static analyzer happy. * - fast-reload review, don't forget to copy tag_actions. * - fast-reload review, less indentation. * - fast-reload review, don't leak respip_actions when reloading. * - fast-reload review, protect NULL pointer dereference in get_mem functions. * - fast-reload review, add fast_reload_most_options.tdir to test most options with high verbosity when fast reloading. * - fast-reload review, don't skip new line on long error printouts. * - fast-reload review, typo. * - fast-reload review, use new_z for consistency. * - fast-reload review, nit for unlock ordering to make eye comparison with the lock counterpart easier. * - fast-reload review, in case of error the sockets are already closed. * - fast-reload review, identation. * - fast-reload review, add static keywords. * - fast-reload review, update unbound-control usage text. * - fast-reload review, updates to the man page. * - fast-reload, the fast-reload command is experimental. * - fast-reload, fix compile of doqclient for fast reload functions. * Changelog comment for #1042 - Merge #1042: Fast Reload. The unbound-control fast_reload is added. It reads changed config in a thread, then only briefly pauses the service threads, that keep running. DNS service is only interrupted briefly, less than a second. --------- Co-authored-by: Yorgos Thessalonikefs --- Makefile.in | 9 +- config.h.in | 3 + configure | 8 + configure.ac | 1 + daemon/acl_list.c | 11 + daemon/acl_list.h | 8 + daemon/daemon.c | 34 +- daemon/daemon.h | 25 +- daemon/remote.c | 4085 ++++++++++++++++- daemon/remote.h | 167 + daemon/unbound.c | 3 + daemon/worker.c | 95 +- daemon/worker.h | 8 +- dnstap/dnstap.c | 28 +- dnstap/dnstap.h | 7 + dnstap/unbound-dnstap-socket.c | 14 + doc/Changelog | 4 + doc/unbound-control.8.in | 87 + iterator/iter_fwd.c | 16 + iterator/iter_fwd.h | 9 + iterator/iter_hints.c | 11 + iterator/iter_hints.h | 9 + iterator/iter_utils.c | 121 +- iterator/iter_utils.h | 38 + iterator/iterator.c | 39 +- iterator/iterator.h | 26 +- libunbound/libworker.c | 14 + respip/respip.c | 72 +- respip/respip.h | 33 +- services/authzone.c | 182 +- services/authzone.h | 36 +- services/cache/infra.c | 94 +- services/cache/infra.h | 15 + services/localzone.c | 32 + services/localzone.h | 14 + services/mesh.c | 144 +- services/mesh.h | 13 + services/rpz.c | 28 + services/rpz.h | 7 + services/view.c | 41 +- services/view.h | 26 +- smallapp/unbound-control.c | 10 + smallapp/worker_cb.c | 14 + testcode/checklocks.c | 22 +- testcode/doqclient.c | 14 + testcode/fake_event.c | 25 + testcode/testbound.c | 11 + testdata/fast_reload_fwd.tdir/auth1.zone | 2 + testdata/fast_reload_fwd.tdir/auth2.zone | 2 + .../fast_reload_fwd.tdir/fast_reload_fwd.conf | 107 + .../fast_reload_fwd.conf2 | 108 + .../fast_reload_fwd.tdir/fast_reload_fwd.dsc | 16 + .../fast_reload_fwd.tdir/fast_reload_fwd.ns1 | 339 ++ .../fast_reload_fwd.tdir/fast_reload_fwd.ns2 | 285 ++ .../fast_reload_fwd.tdir/fast_reload_fwd.post | 25 + .../fast_reload_fwd.tdir/fast_reload_fwd.pre | 56 + .../fast_reload_fwd.tdir/fast_reload_fwd.test | 320 ++ .../auth.nlnetlabs.nl.zone | 3 + .../fast_reload_most_options.conf | 143 + .../fast_reload_most_options.dsc | 16 + .../fast_reload_most_options.post | 11 + .../fast_reload_most_options.pre | 33 + .../fast_reload_most_options.test | 42 + .../rpz.nlnetlabs.nl.zone | 5 + .../fast_reload_thread.conf | 20 + .../fast_reload_thread.dsc | 16 + .../fast_reload_thread.post | 11 + .../fast_reload_thread.pre | 34 + .../fast_reload_thread.test | 38 + util/config_file.c | 8 + util/config_file.h | 3 + util/edns.c | 23 + util/edns.h | 16 + util/fptr_wlist.c | 3 + util/module.h | 5 + util/netevent.c | 8 +- util/netevent.h | 8 + util/storage/lruhash.c | 30 + util/storage/lruhash.h | 10 + util/storage/slabhash.c | 9 + util/storage/slabhash.h | 7 + util/tcp_conn_limit.c | 11 + util/tcp_conn_limit.h | 9 + validator/autotrust.c | 25 +- validator/val_anchor.c | 61 +- validator/val_anchor.h | 10 +- validator/val_neg.c | 9 + validator/val_neg.h | 7 + validator/validator.c | 92 +- validator/validator.h | 23 + 90 files changed, 7471 insertions(+), 251 deletions(-) create mode 100644 testdata/fast_reload_fwd.tdir/auth1.zone create mode 100644 testdata/fast_reload_fwd.tdir/auth2.zone create mode 100644 testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf create mode 100644 testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf2 create mode 100644 testdata/fast_reload_fwd.tdir/fast_reload_fwd.dsc create mode 100644 testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns1 create mode 100644 testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns2 create mode 100644 testdata/fast_reload_fwd.tdir/fast_reload_fwd.post create mode 100644 testdata/fast_reload_fwd.tdir/fast_reload_fwd.pre create mode 100644 testdata/fast_reload_fwd.tdir/fast_reload_fwd.test create mode 100644 testdata/fast_reload_most_options.tdir/auth.nlnetlabs.nl.zone create mode 100644 testdata/fast_reload_most_options.tdir/fast_reload_most_options.conf create mode 100644 testdata/fast_reload_most_options.tdir/fast_reload_most_options.dsc create mode 100644 testdata/fast_reload_most_options.tdir/fast_reload_most_options.post create mode 100644 testdata/fast_reload_most_options.tdir/fast_reload_most_options.pre create mode 100644 testdata/fast_reload_most_options.tdir/fast_reload_most_options.test create mode 100644 testdata/fast_reload_most_options.tdir/rpz.nlnetlabs.nl.zone create mode 100644 testdata/fast_reload_thread.tdir/fast_reload_thread.conf create mode 100644 testdata/fast_reload_thread.tdir/fast_reload_thread.dsc create mode 100644 testdata/fast_reload_thread.tdir/fast_reload_thread.post create mode 100644 testdata/fast_reload_thread.tdir/fast_reload_thread.pre create mode 100644 testdata/fast_reload_thread.tdir/fast_reload_thread.test diff --git a/Makefile.in b/Makefile.in index 9262cefd4..0cdff8ac1 100644 --- a/Makefile.in +++ b/Makefile.in @@ -881,7 +881,7 @@ view.lo view.o: $(srcdir)/services/view.c config.h $(srcdir)/services/view.h $(s $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/services/localzone.h $(srcdir)/util/storage/dnstree.h \ $(srcdir)/util/module.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/data/msgreply.h \ $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/data/msgparse.h $(srcdir)/sldns/pkthdr.h \ - $(srcdir)/sldns/rrdef.h $(srcdir)/sldns/sbuffer.h $(srcdir)/util/config_file.h + $(srcdir)/sldns/rrdef.h $(srcdir)/sldns/sbuffer.h $(srcdir)/util/config_file.h $(srcdir)/respip/respip.h rpz.lo rpz.o: $(srcdir)/services/rpz.c config.h $(srcdir)/services/rpz.h $(srcdir)/services/localzone.h \ $(srcdir)/util/rbtree.h $(srcdir)/util/locks.h $(srcdir)/util/log.h $(srcdir)/util/storage/dnstree.h \ $(srcdir)/util/module.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/data/msgreply.h \ @@ -977,7 +977,7 @@ fptr_wlist.lo fptr_wlist.o: $(srcdir)/util/fptr_wlist.c config.h $(srcdir)/util/ $(srcdir)/validator/val_nsec3.h $(srcdir)/validator/val_sigcrypt.h $(srcdir)/validator/val_kentry.h \ $(srcdir)/validator/val_neg.h $(srcdir)/validator/autotrust.h $(srcdir)/libunbound/libworker.h \ $(srcdir)/libunbound/context.h $(srcdir)/util/alloc.h $(srcdir)/libunbound/unbound-event.h \ - $(srcdir)/libunbound/worker.h + $(srcdir)/libunbound/worker.h $(srcdir)/daemon/remote.h locks.lo locks.o: $(srcdir)/util/locks.c config.h $(srcdir)/util/locks.h $(srcdir)/util/log.h log.lo log.o: $(srcdir)/util/log.c config.h $(srcdir)/util/log.h $(srcdir)/util/locks.h $(srcdir)/sldns/sbuffer.h mini_event.lo mini_event.o: $(srcdir)/util/mini_event.c config.h $(srcdir)/util/mini_event.h $(srcdir)/util/rbtree.h \ @@ -1313,7 +1313,10 @@ remote.lo remote.o: $(srcdir)/daemon/remote.c config.h $(srcdir)/daemon/remote.h $(srcdir)/validator/val_anchor.h $(srcdir)/iterator/iterator.h $(srcdir)/services/outbound_list.h \ $(srcdir)/iterator/iter_fwd.h $(srcdir)/iterator/iter_hints.h $(srcdir)/iterator/iter_delegpt.h \ $(srcdir)/services/outside_network.h $(srcdir)/sldns/str2wire.h $(srcdir)/sldns/parseutil.h \ - $(srcdir)/sldns/wire2str.h $(srcdir)/util/edns.h + $(srcdir)/sldns/wire2str.h $(srcdir)/util/edns.h \ + $(srcdir)/util/locks.h $(srcdir)/util/ub_event.h \ + $(srcdir)/util/tcp_conn_limit.h $(srcdir)/util/edns.h $(srcdir)/validator/val_neg.h \ + $(srcdir)/iterator/iter_utils.h $(srcdir)/iterator/iter_donotq.h $(srcdir)/iterator/iter_priv.h stats.lo stats.o: $(srcdir)/daemon/stats.c config.h $(srcdir)/daemon/stats.h $(srcdir)/util/timehist.h \ $(srcdir)/libunbound/unbound.h $(srcdir)/daemon/worker.h $(srcdir)/libunbound/worker.h $(srcdir)/sldns/sbuffer.h \ $(srcdir)/util/data/packed_rrset.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/log.h \ diff --git a/config.h.in b/config.h.in index dc03e82dd..246f06c2f 100644 --- a/config.h.in +++ b/config.h.in @@ -663,6 +663,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_STDARG_H +/* Define to 1 if you have the header file. */ +#undef HAVE_STDATOMIC_H + /* Define to 1 if you have the header file. */ #undef HAVE_STDBOOL_H diff --git a/configure b/configure index 2f49f0bda..e2276279a 100755 --- a/configure +++ b/configure @@ -16100,6 +16100,14 @@ then : fi +ac_fn_c_check_header_compile "$LINENO" "stdatomic.h" "ac_cv_header_stdatomic_h" "$ac_includes_default +" +if test "x$ac_cv_header_stdatomic_h" = xyes +then : + printf "%s\n" "#define HAVE_STDATOMIC_H 1" >>confdefs.h + +fi + # check for types. # Using own tests for int64* because autoconf builtin only give 32bit. diff --git a/configure.ac b/configure.ac index 07d211e6f..98b8241cf 100644 --- a/configure.ac +++ b/configure.ac @@ -524,6 +524,7 @@ AC_CHECK_HEADERS([netioapi.h],,, [AC_INCLUDES_DEFAULT # Check for Linux timestamping headers AC_CHECK_HEADERS([linux/net_tstamp.h],,, [AC_INCLUDES_DEFAULT]) +AC_CHECK_HEADERS([stdatomic.h],,, [AC_INCLUDES_DEFAULT]) # check for types. # Using own tests for int64* because autoconf builtin only give 32bit. diff --git a/daemon/acl_list.c b/daemon/acl_list.c index 362de0a2c..24cd9b399 100644 --- a/daemon/acl_list.c +++ b/daemon/acl_list.c @@ -816,3 +816,14 @@ log_acl_action(const char* action, struct sockaddr_storage* addr, (int)port); } } + +void acl_list_swap_tree(struct acl_list* acl, struct acl_list* data) +{ + /* swap tree and region */ + rbtree_type oldtree = acl->tree; + struct regional* oldregion = acl->region; + acl->tree = data->tree; + acl->region = data->region; + data->tree = oldtree; + data->region = oldregion; +} diff --git a/daemon/acl_list.h b/daemon/acl_list.h index 6e6e5e2e9..416169ee9 100644 --- a/daemon/acl_list.h +++ b/daemon/acl_list.h @@ -201,4 +201,12 @@ const char* acl_access_to_str(enum acl_access acl); void log_acl_action(const char* action, struct sockaddr_storage* addr, socklen_t addrlen, enum acl_access acl, struct acl_addr* acladdr); +/** + * Swap internal tree with preallocated entries. + * @param acl: the acl structure. + * @param data: the data structure used to take elements from. This contains + * the old elements on return. + */ +void acl_list_swap_tree(struct acl_list* acl, struct acl_list* data); + #endif /* DAEMON_ACL_LIST_H */ diff --git a/daemon/daemon.c b/daemon/daemon.c index 76607fe52..f882bb9ad 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -323,8 +323,7 @@ daemon_init(void) return daemon; } -static int setup_acl_for_ports(struct acl_list* list, - struct listen_port* port_list) +int setup_acl_for_ports(struct acl_list* list, struct listen_port* port_list) { struct acl_addr* acl_node; for(; port_list; port_list=port_list->next) { @@ -717,16 +716,16 @@ daemon_fork(struct daemon* daemon) #endif log_assert(daemon); - if(!(daemon->views = views_create())) + if(!(daemon->env->views = views_create())) fatal_exit("Could not create views: out of memory"); /* create individual views and their localzone/data trees */ - if(!views_apply_cfg(daemon->views, daemon->cfg)) + if(!views_apply_cfg(daemon->env->views, daemon->cfg)) fatal_exit("Could not set up views"); - if(!acl_list_apply_cfg(daemon->acl, daemon->cfg, daemon->views)) + if(!acl_list_apply_cfg(daemon->acl, daemon->cfg, daemon->env->views)) fatal_exit("Could not setup access control list"); if(!acl_interface_apply_cfg(daemon->acl_interface, daemon->cfg, - daemon->views)) + daemon->env->views)) fatal_exit("Could not setup interface control list"); if(!tcl_list_apply_cfg(daemon->tcl, daemon->cfg)) fatal_exit("Could not setup TCP connection limits"); @@ -762,15 +761,15 @@ daemon_fork(struct daemon* daemon) fatal_exit("Could not set root or stub hints"); /* process raw response-ip configuration data */ - if(!(daemon->respip_set = respip_set_create())) + if(!(daemon->env->respip_set = respip_set_create())) fatal_exit("Could not create response IP set"); - if(!respip_global_apply_cfg(daemon->respip_set, daemon->cfg)) + if(!respip_global_apply_cfg(daemon->env->respip_set, daemon->cfg)) fatal_exit("Could not set up response IP set"); - if(!respip_views_apply_cfg(daemon->views, daemon->cfg, + if(!respip_views_apply_cfg(daemon->env->views, daemon->cfg, &have_view_respip_cfg)) fatal_exit("Could not set up per-view response IP sets"); - daemon->use_response_ip = !respip_set_is_empty(daemon->respip_set) || - have_view_respip_cfg; + daemon->use_response_ip = !respip_set_is_empty( + daemon->env->respip_set) || have_view_respip_cfg; /* setup modules */ daemon_setup_modules(daemon); @@ -886,14 +885,18 @@ daemon_cleanup(struct daemon* daemon) daemon->env->hints = NULL; local_zones_delete(daemon->local_zones); daemon->local_zones = NULL; - respip_set_delete(daemon->respip_set); - daemon->respip_set = NULL; - views_delete(daemon->views); - daemon->views = NULL; + respip_set_delete(daemon->env->respip_set); + daemon->env->respip_set = NULL; + views_delete(daemon->env->views); + daemon->env->views = NULL; if(daemon->env->auth_zones) auth_zones_cleanup(daemon->env->auth_zones); /* key cache is cleared by module deinit during next daemon_fork() */ daemon_remote_clear(daemon->rc); + if(daemon->fast_reload_thread) + fast_reload_thread_stop(daemon->fast_reload_thread); + if(daemon->fast_reload_printq_list) + fast_reload_printq_list_delete(daemon->fast_reload_printq_list); for(i=0; inum; i++) worker_delete(daemon->workers[i]); free(daemon->workers); @@ -951,6 +954,7 @@ daemon_delete(struct daemon* daemon) listen_desetup_locks(); free(daemon->chroot); free(daemon->pidfile); + free(daemon->cfgfile); free(daemon->env); #ifdef HAVE_SSL listen_sslctx_delete_ticket_keys(); diff --git a/daemon/daemon.h b/daemon/daemon.h index 54ab97b2d..2295761ab 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -60,6 +60,8 @@ struct respip_set; struct shm_main_info; struct doq_table; struct cookie_secrets; +struct fast_reload_thread; +struct fast_reload_printq; #include "dnstap/dnstap_config.h" #ifdef USE_DNSTAP @@ -137,15 +139,11 @@ struct daemon { struct timeval time_last_stat; /** time when daemon started */ struct timeval time_boot; - /** views structure containing view tree */ - struct views* views; #ifdef USE_DNSTAP /** the dnstap environment master value, copied and changed by threads*/ struct dt_env* dtenv; #endif struct shm_main_info* shm_info; - /** response-ip set with associated actions and tags. */ - struct respip_set* respip_set; /** some response-ip tags or actions are configured if true */ int use_response_ip; /** some RPZ policies are configured */ @@ -160,6 +158,17 @@ struct daemon { int reuse_cache; /** the EDNS cookie secrets from the cookie-secret-file */ struct cookie_secrets* cookie_secrets; + /** the fast reload thread, or NULL */ + struct fast_reload_thread* fast_reload_thread; + /** the fast reload printq list */ + struct fast_reload_printq* fast_reload_printq_list; + /** the fast reload option to drop mesh queries, true if so. */ + int fast_reload_drop_mesh; + /** for fast reload, if the tcl, tcp connection limits, has + * changes for workers */ + int fast_reload_tcl_has_changes; + /** config file name */ + char* cfgfile; }; /** @@ -212,4 +221,12 @@ void daemon_delete(struct daemon* daemon); */ void daemon_apply_cfg(struct daemon* daemon, struct config_file* cfg); +/** + * Setup acl list to have entries for the port list. + * @param list: the acl interface + * @param port_list: list of open ports, or none. + * @return false on failure + */ +int setup_acl_for_ports(struct acl_list* list, struct listen_port* port_list); + #endif /* DAEMON_H */ diff --git a/daemon/remote.c b/daemon/remote.c index 3d683afd1..b4b8abe25 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -52,6 +52,9 @@ #ifdef HAVE_OPENSSL_BN_H #include #endif +#ifdef HAVE_STDATOMIC_H +#include +#endif #include #include "daemon/remote.h" @@ -63,6 +66,7 @@ #include "util/config_file.h" #include "util/net_help.h" #include "util/module.h" +#include "util/ub_event.h" #include "services/listen_dnsport.h" #include "services/cache/rrset.h" #include "services/cache/infra.h" @@ -77,10 +81,14 @@ #include "validator/val_kcache.h" #include "validator/val_kentry.h" #include "validator/val_anchor.h" +#include "validator/val_neg.h" #include "iterator/iterator.h" #include "iterator/iter_fwd.h" #include "iterator/iter_hints.h" #include "iterator/iter_delegpt.h" +#include "iterator/iter_utils.h" +#include "iterator/iter_donotq.h" +#include "iterator/iter_priv.h" #include "services/outbound_list.h" #include "services/outside_network.h" #include "sldns/str2wire.h" @@ -88,6 +96,7 @@ #include "sldns/wire2str.h" #include "sldns/sbuffer.h" #include "util/timeval_func.h" +#include "util/tcp_conn_limit.h" #include "util/edns.h" #ifdef USE_CACHEDB #include "cachedb/cachedb.h" @@ -102,6 +111,9 @@ #ifdef HAVE_NETDB_H #include #endif +#ifdef HAVE_POLL_H +#include +#endif /* just for portability */ #ifdef SQ @@ -114,6 +126,18 @@ /** Acceptable lengths of str lines */ #define MAX_CMD_STRLINE 1024 #define MAX_STDIN_STRLINE 2048 +/** What number of loop iterations is too much for ipc retries */ +#define IPC_LOOP_MAX 200 +/** Timeout in msec for ipc socket poll. */ +#define IPC_NOTIFICATION_WAIT 200 + +static void fr_printq_delete(struct fast_reload_printq* printq); +static void fr_main_perform_printout(struct fast_reload_thread* fr); +static int fr_printq_empty(struct fast_reload_printq* printq); +static void fr_printq_list_insert(struct fast_reload_printq* printq, + struct daemon* daemon); +static void fr_printq_remove(struct fast_reload_printq* printq); +static void fr_check_cmd_from_thread(struct fast_reload_thread* fr); static int remote_setup_ctx(struct daemon_remote* rc, struct config_file* cfg) @@ -512,6 +536,11 @@ state_list_remove_elem(struct rc_state** list, struct comm_point* c) static void clean_point(struct daemon_remote* rc, struct rc_state* s) { + if(!s->rc) { + /* the state has been picked up and moved away */ + free(s); + return; + } state_list_remove_elem(&rc->busy_list, s->c); rc->active --; if(s->ssl) { @@ -679,6 +708,65 @@ do_reload(RES* ssl, struct worker* worker, int reuse_cache) send_ok(ssl); } +#ifndef THREADS_DISABLED +/** parse fast reload command options. */ +static int +fr_parse_options(RES* ssl, char* arg, int* fr_verb, int* fr_nopause, + int* fr_drop_mesh) +{ + char* argp = arg; + while(*argp=='+') { + argp++; + while(*argp!=0 && *argp!=' ' && *argp!='\t') { + if(*argp == 'v') { + (*fr_verb)++; + } else if(*argp == 'p') { + (*fr_nopause) = 1; + } else if(*argp == 'd') { + (*fr_drop_mesh) = 1; + } else { + if(!ssl_printf(ssl, + "error: unknown option '+%c'\n", + *argp)) + return 0; + return 0; + } + argp++; + } + argp = skipwhite(argp); + } + if(*argp!=0) { + if(!ssl_printf(ssl, "error: unknown option '%s'\n", argp)) + return 0; + return 0; + } + return 1; +} +#endif /* !THREADS_DISABLED */ + +/** do the fast_reload command */ +static void +do_fast_reload(RES* ssl, struct worker* worker, struct rc_state* s, char* arg) +{ +#ifdef THREADS_DISABLED + if(!ssl_printf(ssl, "error: no threads for fast_reload, compiled without threads.\n")) + return; + (void)worker; + (void)s; + (void)arg; +#else + int fr_verb = 0, fr_nopause = 0, fr_drop_mesh = 0; + if(!fr_parse_options(ssl, arg, &fr_verb, &fr_nopause, &fr_drop_mesh)) + return; + if(fr_verb >= 1) { + if(!ssl_printf(ssl, "start fast_reload\n")) + return; + } + fast_reload_thread_start(ssl, worker, s, fr_verb, fr_nopause, + fr_drop_mesh); +#endif +} + /** do the verbosity command */ static void do_verbosity(RES* ssl, char* str) @@ -1473,8 +1561,7 @@ do_view_zone_add(RES* ssl, struct worker* worker, char* arg) struct view* v; if(!find_arg2(ssl, arg, &arg2)) return; - v = views_find_view(worker->daemon->views, - arg, 1 /* get write lock*/); + v = views_find_view(worker->env.views, arg, 1 /* get write lock*/); if(!v) { ssl_printf(ssl,"no view with name: %s\n", arg); return; @@ -1506,8 +1593,7 @@ do_view_zone_remove(RES* ssl, struct worker* worker, char* arg) struct view* v; if(!find_arg2(ssl, arg, &arg2)) return; - v = views_find_view(worker->daemon->views, - arg, 1 /* get write lock*/); + v = views_find_view(worker->env.views, arg, 1 /* get write lock*/); if(!v) { ssl_printf(ssl,"no view with name: %s\n", arg); return; @@ -1529,8 +1615,7 @@ do_view_data_add(RES* ssl, struct worker* worker, char* arg) struct view* v; if(!find_arg2(ssl, arg, &arg2)) return; - v = views_find_view(worker->daemon->views, - arg, 1 /* get write lock*/); + v = views_find_view(worker->env.views, arg, 1 /* get write lock*/); if(!v) { ssl_printf(ssl,"no view with name: %s\n", arg); return; @@ -1555,8 +1640,7 @@ do_view_datas_add(struct daemon_remote* rc, RES* ssl, struct worker* worker, char buf[MAX_CMD_STRLINE + MAX_STDIN_STRLINE] = "view_local_data "; size_t cmd_len; int num = 0, line = 0; - v = views_find_view(worker->daemon->views, - arg, 1 /* get write lock*/); + v = views_find_view(worker->env.views, arg, 1 /* get write lock*/); if(!v) { ssl_printf(ssl,"no view with name: %s\n", arg); return; @@ -1597,8 +1681,7 @@ do_view_data_remove(RES* ssl, struct worker* worker, char* arg) struct view* v; if(!find_arg2(ssl, arg, &arg2)) return; - v = views_find_view(worker->daemon->views, - arg, 1 /* get write lock*/); + v = views_find_view(worker->env.views, arg, 1 /* get write lock*/); if(!v) { ssl_printf(ssl,"no view with name: %s\n", arg); return; @@ -1621,8 +1704,7 @@ do_view_datas_remove(struct daemon_remote* rc, RES* ssl, struct worker* worker, char buf[MAX_CMD_STRLINE + MAX_STDIN_STRLINE] = "view_local_data_remove "; int num = 0; size_t cmd_len; - v = views_find_view(worker->daemon->views, - arg, 1 /* get write lock*/); + v = views_find_view(worker->env.views, arg, 1 /* get write lock*/); if(!v) { ssl_printf(ssl,"no view with name: %s\n", arg); return; @@ -3045,7 +3127,7 @@ do_list_local_data(RES* ssl, struct worker* worker, struct local_zones* zones) static void do_view_list_local_zones(RES* ssl, struct worker* worker, char* arg) { - struct view* v = views_find_view(worker->daemon->views, + struct view* v = views_find_view(worker->env.views, arg, 0 /* get read lock*/); if(!v) { ssl_printf(ssl,"no view with name: %s\n", arg); @@ -3061,7 +3143,7 @@ do_view_list_local_zones(RES* ssl, struct worker* worker, char* arg) static void do_view_list_local_data(RES* ssl, struct worker* worker, char* arg) { - struct view* v = views_find_view(worker->daemon->views, + struct view* v = views_find_view(worker->env.views, arg, 0 /* get read lock*/); if(!v) { ssl_printf(ssl,"no view with name: %s\n", arg); @@ -3427,7 +3509,7 @@ cmdcmp(char* p, const char* cmd, size_t len) /** execute a remote control command */ static void -execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd, +execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd, struct worker* worker) { char* p = skipwhite(cmd); @@ -3441,6 +3523,9 @@ execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd, } else if(cmdcmp(p, "reload", 6)) { do_reload(ssl, worker, 0); return; + } else if(cmdcmp(p, "fast_reload", 11)) { + do_fast_reload(ssl, worker, s, skipwhite(p+11)); + return; } else if(cmdcmp(p, "stats_noreset", 13)) { do_stats(ssl, worker, 0); return; @@ -3639,7 +3724,7 @@ daemon_remote_exec(struct worker* worker) return; } verbose(VERB_ALGO, "remote exec distributed: %s", (char*)msg); - execute_cmd(NULL, NULL, (char*)msg, worker); + execute_cmd(NULL, NULL, NULL, (char*)msg, worker); free(msg); } @@ -3703,7 +3788,7 @@ handle_req(struct daemon_remote* rc, struct rc_state* s, RES* res) verbose(VERB_DETAIL, "control cmd: %s", buf); /* figure out what to do */ - execute_cmd(rc, res, buf, rc->worker); + execute_cmd(rc, s, res, buf, rc->worker); } /** handle SSL_do_handshake changes to the file descriptor to wait for later */ @@ -3795,3 +3880,3969 @@ int remote_control_callback(struct comm_point* c, void* arg, int err, clean_point(rc, s); return 0; } + +/** + * This routine polls a socket for readiness. + * @param fd: file descriptor, -1 uses no fd for a timer only. + * @param timeout: time in msec to wait. 0 means nonblocking test, + * -1 waits blocking for events. + * @param pollin: check for input event. + * @param pollout: check for output event. + * @param event: output variable, set to true if the event happens. + * It is false if there was an error or timeout. + * @return false is system call failure, also logged. + */ +static int +sock_poll_timeout(int fd, int timeout, int pollin, int pollout, int* event) +{ + int loopcount = 0; + /* Loop if the system call returns an errno to do so, like EINTR. */ + while(1) { + struct pollfd p, *fds; + int nfds, ret; + if(++loopcount > IPC_LOOP_MAX) { + log_err("sock_poll_timeout: loop"); + if(event) + *event = 0; + return 0; + } + if(fd == -1) { + fds = NULL; + nfds = 0; + } else { + fds = &p; + nfds = 1; + memset(&p, 0, sizeof(p)); + p.fd = fd; + p.events = POLLERR +#ifndef USE_WINSOCK + | POLLHUP +#endif + ; + if(pollin) + p.events |= POLLIN; + if(pollout) + p.events |= POLLOUT; + } +#ifndef USE_WINSOCK + ret = poll(fds, nfds, timeout); +#else + if(fds == NULL) { + Sleep(timeout); + ret = 0; + } else { + ret = WSAPoll(fds, nfds, timeout); + } +#endif + if(ret == -1) { + if( +#ifndef USE_WINSOCK + errno == EINTR || errno == EAGAIN +# ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +# endif +#else + WSAGetLastError() == WSAEINTR || + WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK +#endif + ) + continue; /* Try again. */ + log_err("poll: %s", sock_strerror(errno)); + if(event) + *event = 0; + return 0; + } else if(ret == 0) { + /* Timeout */ + if(event) + *event = 0; + return 1; + } + break; + } + if(event) + *event = 1; + return 1; +} + +/** fast reload convert fast reload notification status to string */ +static const char* +fr_notification_to_string(enum fast_reload_notification status) +{ + switch(status) { + case fast_reload_notification_none: + return "none"; + case fast_reload_notification_done: + return "done"; + case fast_reload_notification_done_error: + return "done_error"; + case fast_reload_notification_exit: + return "exit"; + case fast_reload_notification_exited: + return "exited"; + case fast_reload_notification_printout: + return "printout"; + case fast_reload_notification_reload_stop: + return "reload_stop"; + case fast_reload_notification_reload_ack: + return "reload_ack"; + case fast_reload_notification_reload_nopause_poll: + return "reload_nopause_poll"; + case fast_reload_notification_reload_start: + return "reload_start"; + default: + break; + } + return "unknown"; +} + +#ifndef THREADS_DISABLED +/** fast reload, poll for notification incoming. True if quit */ +static int +fr_poll_for_quit(struct fast_reload_thread* fr) +{ + int inevent, loopexit = 0, bcount = 0; + uint32_t cmd; + ssize_t ret; + + if(fr->need_to_quit) + return 1; + /* Is there data? */ + if(!sock_poll_timeout(fr->commpair[1], 0, 1, 0, &inevent)) { + log_err("fr_poll_for_quit: poll failed"); + return 0; + } + if(!inevent) + return 0; + + /* Read the data */ + while(1) { + if(++loopexit > IPC_LOOP_MAX) { + log_err("fr_poll_for_quit: recv loops %s", + sock_strerror(errno)); + return 0; + } + ret = recv(fr->commpair[1], ((char*)&cmd)+bcount, + sizeof(cmd)-bcount, 0); + if(ret == -1) { + if( +#ifndef USE_WINSOCK + errno == EINTR || errno == EAGAIN +# ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +# endif +#else + WSAGetLastError() == WSAEINTR || + WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK +#endif + ) + continue; /* Try again. */ + log_err("fr_poll_for_quit: recv: %s", + sock_strerror(errno)); + return 0; + } else if(ret+(ssize_t)bcount != sizeof(cmd)) { + bcount += ret; + if((size_t)bcount < sizeof(cmd)) + continue; + } + break; + } + if(cmd == fast_reload_notification_exit) { + fr->need_to_quit = 1; + verbose(VERB_ALGO, "fast reload: exit notification received"); + return 1; + } + log_err("fr_poll_for_quit: unknown notification status received: %d %s", + cmd, fr_notification_to_string(cmd)); + return 0; +} + +/** fast reload thread. Send notification from the fast reload thread */ +static void +fr_send_notification(struct fast_reload_thread* fr, + enum fast_reload_notification status) +{ + int outevent, loopexit = 0, bcount = 0; + uint32_t cmd; + ssize_t ret; + verbose(VERB_ALGO, "fast reload: send notification %s", + fr_notification_to_string(status)); + /* Make a blocking attempt to send. But meanwhile stay responsive, + * once in a while for quit commands. In case the server has to quit. */ + /* see if there is incoming quit signals */ + if(fr_poll_for_quit(fr)) + return; + cmd = status; + while(1) { + if(++loopexit > IPC_LOOP_MAX) { + log_err("fast reload: could not send notification"); + return; + } + /* wait for socket to become writable */ + if(!sock_poll_timeout(fr->commpair[1], IPC_NOTIFICATION_WAIT, + 0, 1, &outevent)) { + log_err("fast reload: poll failed"); + return; + } + if(fr_poll_for_quit(fr)) + return; + if(!outevent) + continue; + ret = send(fr->commpair[1], ((char*)&cmd)+bcount, + sizeof(cmd)-bcount, 0); + if(ret == -1) { + if( +#ifndef USE_WINSOCK + errno == EINTR || errno == EAGAIN +# ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +# endif +#else + WSAGetLastError() == WSAEINTR || + WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK +#endif + ) + continue; /* Try again. */ + log_err("fast reload send notification: send: %s", + sock_strerror(errno)); + return; + } else if(ret+(ssize_t)bcount != sizeof(cmd)) { + bcount += ret; + if((size_t)bcount < sizeof(cmd)) + continue; + } + break; + } +} + +/** fast reload thread queue up text string for output */ +static int +fr_output_text(struct fast_reload_thread* fr, const char* msg) +{ + char* item = strdup(msg); + if(!item) { + log_err("fast reload output text: strdup out of memory"); + return 0; + } + lock_basic_lock(&fr->fr_output_lock); + if(!cfg_strlist_append(fr->fr_output, item)) { + lock_basic_unlock(&fr->fr_output_lock); + /* The item is freed by cfg_strlist_append on failure. */ + log_err("fast reload output text: append out of memory"); + return 0; + } + lock_basic_unlock(&fr->fr_output_lock); + return 1; +} + +/** fast reload thread output vmsg function */ +static int +fr_output_vmsg(struct fast_reload_thread* fr, const char* format, va_list args) +{ + char msg[1024]; + vsnprintf(msg, sizeof(msg), format, args); + return fr_output_text(fr, msg); +} + +/** fast reload thread printout function, with printf arguments */ +static int fr_output_printf(struct fast_reload_thread* fr, + const char* format, ...) ATTR_FORMAT(printf, 2, 3); + +/** fast reload thread printout function, prints to list and signals + * the remote control thread to move that to get written to the socket + * of the remote control connection. */ +static int +fr_output_printf(struct fast_reload_thread* fr, const char* format, ...) +{ + va_list args; + int ret; + va_start(args, format); + ret = fr_output_vmsg(fr, format, args); + va_end(args); + return ret; +} + +/** fast reload thread, init time counters */ +static void +fr_init_time(struct timeval* time_start, struct timeval* time_read, + struct timeval* time_construct, struct timeval* time_reload, + struct timeval* time_end) +{ + memset(time_start, 0, sizeof(*time_start)); + memset(time_read, 0, sizeof(*time_read)); + memset(time_construct, 0, sizeof(*time_construct)); + memset(time_reload, 0, sizeof(*time_reload)); + memset(time_end, 0, sizeof(*time_end)); + if(gettimeofday(time_start, NULL) < 0) + log_err("gettimeofday: %s", strerror(errno)); +} + +/** + * Structure with constructed elements for use during fast reload. + * At the start it contains the tree items for the new config. + * After the tree items are swapped into the server, the old elements + * are kept in here. They can then be deleted. + */ +struct fast_reload_construct { + /** construct for views */ + struct views* views; + /** construct for auth zones */ + struct auth_zones* auth_zones; + /** construct for forwards */ + struct iter_forwards* fwds; + /** construct for stubs */ + struct iter_hints* hints; + /** construct for respip_set */ + struct respip_set* respip_set; + /** construct for access control */ + struct acl_list* acl; + /** construct for access control interface */ + struct acl_list* acl_interface; + /** construct for tcp connection limit */ + struct tcl_list* tcl; + /** construct for local zones */ + struct local_zones* local_zones; + /** if there is response ip configuration in use */ + int use_response_ip; + /** if there is an rpz zone */ + int use_rpz; + /** construct for edns strings */ + struct edns_strings* edns_strings; + /** construct for trust anchors */ + struct val_anchors* anchors; + /** construct for nsec3 key size */ + size_t* nsec3_keysize; + /** construct for nsec3 max iter */ + size_t* nsec3_maxiter; + /** construct for nsec3 keyiter count */ + int nsec3_keyiter_count; + /** construct for target fetch policy */ + int* target_fetch_policy; + /** construct for max dependency depth */ + int max_dependency_depth; + /** construct for donotquery addresses */ + struct iter_donotq* donotq; + /** construct for private addresses and domains */ + struct iter_priv* priv; + /** construct whitelist for capsforid names */ + struct rbtree_type* caps_white; + /** construct for nat64 */ + struct iter_nat64 nat64; + /** construct for wait_limits_netblock */ + struct rbtree_type wait_limits_netblock; + /** construct for wait_limits_cookie_netblock */ + struct rbtree_type wait_limits_cookie_netblock; + /** construct for domain limits */ + struct rbtree_type domain_limits; + /** storage for the old configuration elements. The outer struct + * is allocated with malloc here, the items are from config. */ + struct config_file* oldcfg; +}; + +/** fast reload thread, read config */ +static int +fr_read_config(struct fast_reload_thread* fr, struct config_file** newcfg) +{ + /* Create new config structure. */ + *newcfg = config_create(); + if(!*newcfg) { + if(!fr_output_printf(fr, "config_create failed: out of memory\n")) + return 0; + fr_send_notification(fr, fast_reload_notification_printout); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + + /* Read new config from file */ + if(!config_read(*newcfg, fr->worker->daemon->cfgfile, + fr->worker->daemon->chroot)) { + config_delete(*newcfg); + if(!fr_output_printf(fr, "config_read %s failed: %s\n", + fr->worker->daemon->cfgfile, strerror(errno))) + return 0; + fr_send_notification(fr, fast_reload_notification_printout); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + if(fr->fr_verb >= 1) { + if(!fr_output_printf(fr, "done read config file %s\n", + fr->worker->daemon->cfgfile)) + return 0; + fr_send_notification(fr, fast_reload_notification_printout); + } + + return 1; +} + +/** Check if two taglists are equal. */ +static int +taglist_equal(char** tagname_a, int num_tags_a, char** tagname_b, + int num_tags_b) +{ + int i; + if(num_tags_a != num_tags_b) + return 0; + for(i=0; i= num_tags_b) + return 0; + /* So, b is longer than a. Check if the initial start of the two + * taglists is the same. */ + if(!taglist_equal(tagname_a, num_tags_a, tagname_b, num_tags_a)) + return 0; + return 1; +} + +/** fast reload thread, check tag defines. */ +static int +fr_check_tag_defines(struct fast_reload_thread* fr, struct config_file* newcfg) +{ + /* The tags are kept in a bitlist for items. Some of them are stored + * in query info. If the tags change, then the old values are + * inaccurate. The solution is to then flush the query list. + * Unless the change only involves adding new tags at the end, that + * needs no changes. */ + if(!taglist_equal(fr->worker->daemon->cfg->tagname, + fr->worker->daemon->cfg->num_tags, newcfg->tagname, + newcfg->num_tags) && + !taglist_change_at_end(fr->worker->daemon->cfg->tagname, + fr->worker->daemon->cfg->num_tags, newcfg->tagname, + newcfg->num_tags)) { + /* The tags have changed too much, the define-tag config. */ + if(fr->fr_drop_mesh) + return 1; /* already dropping queries */ + fr->fr_drop_mesh = 1; + fr->worker->daemon->fast_reload_drop_mesh = fr->fr_drop_mesh; + if(!fr_output_printf(fr, "tags have changed, with " + "'define-tag', and the queries have to be dropped " + "for consistency, setting '+d'\n")) + return 0; + fr_send_notification(fr, fast_reload_notification_printout); + } + return 1; +} + +/** fast reload thread, check if config item has changed, if not add to + * the explanatory string. */ +static void +fr_check_changed_cfg(int cmp, const char* desc, char* str, size_t len) +{ + if(cmp) { + size_t slen = strlen(str); + size_t desclen = strlen(desc); + if(slen == 0) { + snprintf(str, len, "%s", desc); + return; + } + if(len - slen < desclen+2) + return; /* It does not fit */ + snprintf(str+slen, len-slen, " %s", desc); + } +} + +/** fast reload thread, check if config string has changed, checks NULLs. */ +static void +fr_check_changed_cfg_str(char* cmp1, char* cmp2, const char* desc, char* str, + size_t len) +{ + if((!cmp1 && cmp2) || + (cmp1 && !cmp2) || + (cmp1 && cmp2 && strcmp(cmp1, cmp2) != 0)) { + fr_check_changed_cfg(1, desc, str, len); + } +} + +/** fast reload thread, check if config strlist has changed. */ +static void +fr_check_changed_cfg_strlist(struct config_strlist* cmp1, + struct config_strlist* cmp2, const char* desc, char* str, size_t len) +{ + struct config_strlist* p1 = cmp1, *p2 = cmp2; + while(p1 && p2) { + if((!p1->str && p2->str) || + (p1->str && !p2->str) || + (p1->str && p2->str && strcmp(p1->str, p2->str) != 0)) { + /* The strlist is different. */ + fr_check_changed_cfg(1, desc, str, len); + return; + } + p1 = p1->next; + p2 = p2->next; + } + if((!p1 && p2) || (p1 && !p2)) { + fr_check_changed_cfg(1, desc, str, len); + } +} + +/** fast reload thread, check if config str2list has changed. */ +static void +fr_check_changed_cfg_str2list(struct config_str2list* cmp1, + struct config_str2list* cmp2, const char* desc, char* str, size_t len) +{ + struct config_str2list* p1 = cmp1, *p2 = cmp2; + while(p1 && p2) { + if((!p1->str && p2->str) || + (p1->str && !p2->str) || + (p1->str && p2->str && strcmp(p1->str, p2->str) != 0)) { + /* The str2list is different. */ + fr_check_changed_cfg(1, desc, str, len); + return; + } + if((!p1->str2 && p2->str2) || + (p1->str2 && !p2->str2) || + (p1->str2 && p2->str2 && + strcmp(p1->str2, p2->str2) != 0)) { + /* The str2list is different. */ + fr_check_changed_cfg(1, desc, str, len); + return; + } + p1 = p1->next; + p2 = p2->next; + } + if((!p1 && p2) || (p1 && !p2)) { + fr_check_changed_cfg(1, desc, str, len); + } +} + +/** fast reload thread, check compatible config items */ +static int +fr_check_compat_cfg(struct fast_reload_thread* fr, struct config_file* newcfg) +{ + int i; + char changed_str[1024]; + struct config_file* cfg = fr->worker->env.cfg; + changed_str[0]=0; + + /* Find incompatible options, and if so, print an error. */ + fr_check_changed_cfg(cfg->num_threads != newcfg->num_threads, + "num-threads", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->do_ip4 != newcfg->do_ip4, + "do-ip4", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->do_ip6 != newcfg->do_ip6, + "do-ip6", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->do_udp != newcfg->do_udp, + "do-udp", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->do_tcp != newcfg->do_tcp, + "do-tcp", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->port != newcfg->port, + "port", changed_str, sizeof(changed_str)); + /* But cfg->outgoing_num_ports has been changed at startup, + * possibly to reduce it, so do not check it here. */ + fr_check_changed_cfg(cfg->outgoing_num_tcp != newcfg->outgoing_num_tcp, + "outgoing-num-tcp", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->incoming_num_tcp != newcfg->incoming_num_tcp, + "incoming-num-tcp", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->num_out_ifs != newcfg->num_out_ifs, + "outgoing-interface", changed_str, sizeof(changed_str)); + if(cfg->num_out_ifs == newcfg->num_out_ifs) { + for(i=0; inum_out_ifs; i++) + fr_check_changed_cfg(strcmp(cfg->out_ifs[i], + newcfg->out_ifs[i]) != 0, "outgoing-interface", + changed_str, sizeof(changed_str)); + } + fr_check_changed_cfg(cfg->num_ifs != newcfg->num_ifs, + "interface", changed_str, sizeof(changed_str)); + if(cfg->num_ifs == newcfg->num_ifs) { + for(i=0; inum_ifs; i++) + fr_check_changed_cfg(strcmp(cfg->ifs[i], + newcfg->ifs[i]) != 0, "interface", + changed_str, sizeof(changed_str)); + } + fr_check_changed_cfg(cfg->if_automatic != newcfg->if_automatic, + "interface-automatic", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->so_rcvbuf != newcfg->so_rcvbuf, + "so-rcvbuf", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->so_sndbuf != newcfg->so_sndbuf, + "so-sndbuf", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->so_reuseport != newcfg->so_reuseport, + "so-reuseport", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->ip_transparent != newcfg->ip_transparent, + "ip-transparent", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->ip_freebind != newcfg->ip_freebind, + "ip-freebind", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->udp_connect != newcfg->udp_connect, + "udp-connect", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->msg_buffer_size != newcfg->msg_buffer_size, + "msg-buffer-size", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->do_tcp_keepalive != newcfg->do_tcp_keepalive, + "edns-tcp-keepalive", changed_str, sizeof(changed_str)); + fr_check_changed_cfg( + cfg->tcp_keepalive_timeout != newcfg->tcp_keepalive_timeout, + "edns-tcp-keepalive-timeout", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->tcp_idle_timeout != newcfg->tcp_idle_timeout, + "tcp-idle-timeout", changed_str, sizeof(changed_str)); + /* Not changed, only if DoH is used, it is then stored in commpoints, + * as well as used from cfg. */ + fr_check_changed_cfg( + cfg->harden_large_queries != newcfg->harden_large_queries, + "harden-large-queries", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->http_max_streams != newcfg->http_max_streams, + "http-max-streams", changed_str, sizeof(changed_str)); + fr_check_changed_cfg_str(cfg->http_endpoint, newcfg->http_endpoint, + "http-endpoint", changed_str, sizeof(changed_str)); + fr_check_changed_cfg( + cfg->http_notls_downstream != newcfg->http_notls_downstream, + "http_notls_downstream", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->https_port != newcfg->https_port, + "https-port", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->ssl_port != newcfg->ssl_port, + "tls-port", changed_str, sizeof(changed_str)); + fr_check_changed_cfg_str(cfg->ssl_service_key, newcfg->ssl_service_key, + "tls-service-key", changed_str, sizeof(changed_str)); + fr_check_changed_cfg_str(cfg->ssl_service_pem, newcfg->ssl_service_pem, + "tls-service-pem", changed_str, sizeof(changed_str)); + fr_check_changed_cfg_str(cfg->tls_cert_bundle, newcfg->tls_cert_bundle, + "tls-cert-bundle", changed_str, sizeof(changed_str)); + fr_check_changed_cfg_strlist(cfg->proxy_protocol_port, + newcfg->proxy_protocol_port, "proxy-protocol-port", + changed_str, sizeof(changed_str)); + fr_check_changed_cfg_strlist(cfg->tls_additional_port, + newcfg->tls_additional_port, "tls-additional-port", + changed_str, sizeof(changed_str)); + fr_check_changed_cfg_str(cfg->if_automatic_ports, + newcfg->if_automatic_ports, "interface-automatic-ports", + changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->udp_upstream_without_downstream != + newcfg->udp_upstream_without_downstream, + "udp-upstream-without-downstream", changed_str, + sizeof(changed_str)); + + if(changed_str[0] != 0) { + /* The new config changes some items that do not work with + * fast reload. */ + if(!fr_output_printf(fr, "The config changes items that are " + "not compatible with fast_reload, perhaps do reload " + "or restart: %s", changed_str) || + !fr_output_printf(fr, "\n")) + return 0; + fr_send_notification(fr, fast_reload_notification_printout); + return 0; + } + return 1; +} + +/** fast reload thread, check nopause config items */ +static int +fr_check_nopause_cfg(struct fast_reload_thread* fr, struct config_file* newcfg) +{ + char changed_str[1024]; + struct config_file* cfg = fr->worker->env.cfg; + if(!fr->fr_nopause) + return 1; /* The nopause is not enabled, so no problem. */ + changed_str[0]=0; + + /* Check for iter_env. */ + fr_check_changed_cfg( + cfg->outbound_msg_retry != newcfg->outbound_msg_retry, + "outbound-msg-retry", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->max_sent_count != newcfg->max_sent_count, + "max-sent-count", changed_str, sizeof(changed_str)); + fr_check_changed_cfg( + cfg->max_query_restarts != newcfg->max_query_restarts, + "max-query-restarts", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(strcmp(cfg->target_fetch_policy, + newcfg->target_fetch_policy) != 0, + "target-fetch-policy", changed_str, sizeof(changed_str)); + fr_check_changed_cfg( + cfg->donotquery_localhost != newcfg->donotquery_localhost, + "do-not-query-localhost", changed_str, sizeof(changed_str)); + fr_check_changed_cfg_strlist(cfg->donotqueryaddrs, + newcfg->donotqueryaddrs, "do-not-query-localhost", + changed_str, sizeof(changed_str)); + fr_check_changed_cfg_strlist(cfg->private_address, + newcfg->private_address, "private-address", + changed_str, sizeof(changed_str)); + fr_check_changed_cfg_strlist(cfg->private_domain, + newcfg->private_domain, "private-domain", + changed_str, sizeof(changed_str)); + fr_check_changed_cfg_strlist(cfg->caps_whitelist, + newcfg->caps_whitelist, "caps-exempt", + changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->do_nat64 != newcfg->do_nat64, + "do-nat64", changed_str, sizeof(changed_str)); + fr_check_changed_cfg_str(cfg->nat64_prefix, newcfg->nat64_prefix, + "nat64-prefix", changed_str, sizeof(changed_str)); + + /* Check for val_env. */ + fr_check_changed_cfg(cfg->bogus_ttl != newcfg->bogus_ttl, + "val-bogus-ttl", changed_str, sizeof(changed_str)); + fr_check_changed_cfg( + cfg->val_date_override != newcfg->val_date_override, + "val-date-override", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->val_sig_skew_min != newcfg->val_sig_skew_min, + "val-sig-skew-min", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->val_sig_skew_max != newcfg->val_sig_skew_max, + "val-sig-skew-max", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(cfg->val_max_restart != newcfg->val_max_restart, + "val-max-restart", changed_str, sizeof(changed_str)); + fr_check_changed_cfg(strcmp(cfg->val_nsec3_key_iterations, + newcfg->val_nsec3_key_iterations) != 0, + "val-nsec3-keysize-iterations", changed_str, + sizeof(changed_str)); + + /* Check for infra. */ + fr_check_changed_cfg(cfg->host_ttl != newcfg->host_ttl, + "infra-host-ttl", changed_str, sizeof(changed_str)); + fr_check_changed_cfg( + cfg->infra_keep_probing != newcfg->infra_keep_probing, + "infra-keep-probing", changed_str, sizeof(changed_str)); + fr_check_changed_cfg( + cfg->ratelimit != newcfg->ratelimit, + "ratelimit", changed_str, sizeof(changed_str)); + fr_check_changed_cfg( + cfg->ip_ratelimit != newcfg->ip_ratelimit, + "ip-ratelimit", changed_str, sizeof(changed_str)); + fr_check_changed_cfg( + cfg->ip_ratelimit_cookie != newcfg->ip_ratelimit_cookie, + "ip-ratelimit-cookie", changed_str, sizeof(changed_str)); + fr_check_changed_cfg_str2list(cfg->wait_limit_netblock, + newcfg->wait_limit_netblock, "wait-limit-netblock", + changed_str, sizeof(changed_str)); + fr_check_changed_cfg_str2list(cfg->wait_limit_cookie_netblock, + newcfg->wait_limit_cookie_netblock, + "wait-limit-cookie-netblock", changed_str, + sizeof(changed_str)); + fr_check_changed_cfg_str2list(cfg->ratelimit_below_domain, + newcfg->ratelimit_below_domain, "ratelimit-below-domain", + changed_str, sizeof(changed_str)); + fr_check_changed_cfg_str2list(cfg->ratelimit_for_domain, + newcfg->ratelimit_for_domain, "ratelimit-for-domain", + changed_str, sizeof(changed_str)); + + /* Check for dnstap. */ + fr_check_changed_cfg( + cfg->dnstap_send_identity != newcfg->dnstap_send_identity, + "dnstap-send-identity", changed_str, sizeof(changed_str)); + fr_check_changed_cfg( + cfg->dnstap_send_version != newcfg->dnstap_send_version, + "dnstap-send-version", changed_str, sizeof(changed_str)); + fr_check_changed_cfg_str(cfg->dnstap_identity, newcfg->dnstap_identity, + "dnstap-identity", changed_str, sizeof(changed_str)); + fr_check_changed_cfg_str(cfg->dnstap_version, newcfg->dnstap_version, + "dnstap-version", changed_str, sizeof(changed_str)); + + if(changed_str[0] != 0) { + /* The new config changes some items that need a pause, + * to be able to update the variables. */ + if(!fr_output_printf(fr, "The config changes items that need " + "the fast_reload +p option, for nopause, " + "disabled to be reloaded: %s", changed_str) || + !fr_output_printf(fr, "\n")) + return 0; + fr_send_notification(fr, fast_reload_notification_printout); + return 0; + } + return 1; +} + +/** fast reload thread, clear construct information, deletes items */ +static void +fr_construct_clear(struct fast_reload_construct* ct) +{ + if(!ct) + return; + auth_zones_delete(ct->auth_zones); + forwards_delete(ct->fwds); + hints_delete(ct->hints); + respip_set_delete(ct->respip_set); + local_zones_delete(ct->local_zones); + acl_list_delete(ct->acl); + acl_list_delete(ct->acl_interface); + tcl_list_delete(ct->tcl); + edns_strings_delete(ct->edns_strings); + anchors_delete(ct->anchors); + views_delete(ct->views); + free(ct->nsec3_keysize); + free(ct->nsec3_maxiter); + free(ct->target_fetch_policy); + donotq_delete(ct->donotq); + priv_delete(ct->priv); + caps_white_delete(ct->caps_white); + wait_limits_free(&ct->wait_limits_netblock); + wait_limits_free(&ct->wait_limits_cookie_netblock); + domain_limits_free(&ct->domain_limits); + /* Delete the log identity here so that the global value is not + * reset by config_delete. */ + if(ct->oldcfg && ct->oldcfg->log_identity) { + free(ct->oldcfg->log_identity); + ct->oldcfg->log_identity = NULL; + } + config_delete(ct->oldcfg); +} + +/** get memory for strlist */ +static size_t +getmem_config_strlist(struct config_strlist* p) +{ + size_t m = 0; + struct config_strlist* s; + for(s = p; s; s = s->next) + m += sizeof(*s) + getmem_str(s->str); + return m; +} + +/** get memory for str2list */ +static size_t +getmem_config_str2list(struct config_str2list* p) +{ + size_t m = 0; + struct config_str2list* s; + for(s = p; s; s = s->next) + m += sizeof(*s) + getmem_str(s->str) + getmem_str(s->str2); + return m; +} + +/** get memory for str3list */ +static size_t +getmem_config_str3list(struct config_str3list* p) +{ + size_t m = 0; + struct config_str3list* s; + for(s = p; s; s = s->next) + m += sizeof(*s) + getmem_str(s->str) + getmem_str(s->str2) + + getmem_str(s->str3); + return m; +} + +/** get memory for strbytelist */ +static size_t +getmem_config_strbytelist(struct config_strbytelist* p) +{ + size_t m = 0; + struct config_strbytelist* s; + for(s = p; s; s = s->next) + m += sizeof(*s) + getmem_str(s->str) + (s->str2?s->str2len:0); + return m; +} + +/** get memory used by ifs array */ +static size_t +getmem_ifs(int numifs, char** ifs) +{ + size_t m = 0; + int i; + m += numifs * sizeof(char*); + for(i=0; inext) + m += sizeof(*s) + getmem_str(s->name) + + getmem_config_strlist(s->hosts) + + getmem_config_strlist(s->addrs); + return m; +} + +/** get memory for config_auth */ +static size_t +getmem_config_auth(struct config_auth* p) +{ + size_t m = 0; + struct config_auth* s; + for(s = p; s; s = s->next) + m += sizeof(*s) + getmem_str(s->name) + + getmem_config_strlist(s->masters) + + getmem_config_strlist(s->urls) + + getmem_config_strlist(s->allow_notify) + + getmem_str(s->zonefile) + + s->rpz_taglistlen + + getmem_str(s->rpz_action_override) + + getmem_str(s->rpz_log_name) + + getmem_str(s->rpz_cname); + return m; +} + +/** get memory for config_view */ +static size_t +getmem_config_view(struct config_view* p) +{ + size_t m = 0; + struct config_view* s; + for(s = p; s; s = s->next) + m += sizeof(*s) + getmem_str(s->name) + + getmem_config_str2list(s->local_zones) + + getmem_config_strlist(s->local_data) + + getmem_config_strlist(s->local_zones_nodefault) +#ifdef USE_IPSET + + getmem_config_strlist(s->local_zones_ipset) +#endif + + getmem_config_str2list(s->respip_actions) + + getmem_config_str2list(s->respip_data); + + return m; +} + +/** get memory used by config_file item, estimate */ +static size_t +config_file_getmem(struct config_file* cfg) +{ + size_t m = 0; + m += sizeof(*cfg); + m += getmem_config_strlist(cfg->proxy_protocol_port); + m += getmem_str(cfg->ssl_service_key); + m += getmem_str(cfg->ssl_service_pem); + m += getmem_str(cfg->tls_cert_bundle); + m += getmem_config_strlist(cfg->tls_additional_port); + m += getmem_config_strlist(cfg->tls_session_ticket_keys.first); + m += getmem_str(cfg->tls_ciphers); + m += getmem_str(cfg->tls_ciphersuites); + m += getmem_str(cfg->http_endpoint); + m += (cfg->outgoing_avail_ports?65536*sizeof(int):0); + m += getmem_str(cfg->target_fetch_policy); + m += getmem_str(cfg->if_automatic_ports); + m += getmem_ifs(cfg->num_ifs, cfg->ifs); + m += getmem_ifs(cfg->num_out_ifs, cfg->out_ifs); + m += getmem_config_strlist(cfg->root_hints); + m += getmem_config_stub(cfg->stubs); + m += getmem_config_stub(cfg->forwards); + m += getmem_config_auth(cfg->auths); + m += getmem_config_view(cfg->views); + m += getmem_config_strlist(cfg->donotqueryaddrs); +#ifdef CLIENT_SUBNET + m += getmem_config_strlist(cfg->client_subnet); + m += getmem_config_strlist(cfg->client_subnet_zone); +#endif + m += getmem_config_str2list(cfg->acls); + m += getmem_config_str2list(cfg->tcp_connection_limits); + m += getmem_config_strlist(cfg->caps_whitelist); + m += getmem_config_strlist(cfg->private_address); + m += getmem_config_strlist(cfg->private_domain); + m += getmem_str(cfg->chrootdir); + m += getmem_str(cfg->username); + m += getmem_str(cfg->directory); + m += getmem_str(cfg->logfile); + m += getmem_str(cfg->pidfile); + m += getmem_str(cfg->log_identity); + m += getmem_str(cfg->identity); + m += getmem_str(cfg->version); + m += getmem_str(cfg->http_user_agent); + m += getmem_str(cfg->nsid_cfg_str); + m += (cfg->nsid?cfg->nsid_len:0); + m += getmem_str(cfg->module_conf); + m += getmem_config_strlist(cfg->trust_anchor_file_list); + m += getmem_config_strlist(cfg->trust_anchor_list); + m += getmem_config_strlist(cfg->auto_trust_anchor_file_list); + m += getmem_config_strlist(cfg->trusted_keys_file_list); + m += getmem_config_strlist(cfg->domain_insecure); + m += getmem_str(cfg->val_nsec3_key_iterations); + m += getmem_config_str2list(cfg->local_zones); + m += getmem_config_strlist(cfg->local_zones_nodefault); +#ifdef USE_IPSET + m += getmem_config_strlist(cfg->local_zones_ipset); +#endif + m += getmem_config_strlist(cfg->local_data); + m += getmem_config_str3list(cfg->local_zone_overrides); + m += getmem_config_strbytelist(cfg->local_zone_tags); + m += getmem_config_strbytelist(cfg->acl_tags); + m += getmem_config_str3list(cfg->acl_tag_actions); + m += getmem_config_str3list(cfg->acl_tag_datas); + m += getmem_config_str2list(cfg->acl_view); + m += getmem_config_str2list(cfg->interface_actions); + m += getmem_config_strbytelist(cfg->interface_tags); + m += getmem_config_str3list(cfg->interface_tag_actions); + m += getmem_config_str3list(cfg->interface_tag_datas); + m += getmem_config_str2list(cfg->interface_view); + m += getmem_config_strbytelist(cfg->respip_tags); + m += getmem_config_str2list(cfg->respip_actions); + m += getmem_config_str2list(cfg->respip_data); + m += getmem_ifs(cfg->num_tags, cfg->tagname); + m += getmem_config_strlist(cfg->control_ifs.first); + m += getmem_str(cfg->server_key_file); + m += getmem_str(cfg->server_cert_file); + m += getmem_str(cfg->control_key_file); + m += getmem_str(cfg->control_cert_file); + m += getmem_config_strlist(cfg->python_script); + m += getmem_config_strlist(cfg->dynlib_file); + m += getmem_str(cfg->dns64_prefix); + m += getmem_config_strlist(cfg->dns64_ignore_aaaa); + m += getmem_str(cfg->nat64_prefix); + m += getmem_str(cfg->dnstap_socket_path); + m += getmem_str(cfg->dnstap_ip); + m += getmem_str(cfg->dnstap_tls_server_name); + m += getmem_str(cfg->dnstap_tls_cert_bundle); + m += getmem_str(cfg->dnstap_tls_client_key_file); + m += getmem_str(cfg->dnstap_tls_client_cert_file); + m += getmem_str(cfg->dnstap_identity); + m += getmem_str(cfg->dnstap_version); + m += getmem_config_str2list(cfg->ratelimit_for_domain); + m += getmem_config_str2list(cfg->ratelimit_below_domain); + m += getmem_config_str2list(cfg->edns_client_strings); + m += getmem_str(cfg->dnscrypt_provider); + m += getmem_config_strlist(cfg->dnscrypt_secret_key); + m += getmem_config_strlist(cfg->dnscrypt_provider_cert); + m += getmem_config_strlist(cfg->dnscrypt_provider_cert_rotated); +#ifdef USE_IPSECMOD + m += getmem_config_strlist(cfg->ipsecmod_whitelist); + m += getmem_str(cfg->ipsecmod_hook); +#endif +#ifdef USE_CACHEDB + m += getmem_str(cfg->cachedb_backend); + m += getmem_str(cfg->cachedb_secret); +#ifdef USE_REDIS + m += getmem_str(cfg->redis_server_host); + m += getmem_str(cfg->redis_server_path); + m += getmem_str(cfg->redis_server_password); +#endif +#endif +#ifdef USE_IPSET + m += getmem_str(cfg->ipset_name_v4); + m += getmem_str(cfg->ipset_name_v6); +#endif + return m; +} + +/** fast reload thread, print memory used by construct of items. */ +static int +fr_printmem(struct fast_reload_thread* fr, + struct config_file* newcfg, struct fast_reload_construct* ct) +{ + size_t mem = 0; + if(fr_poll_for_quit(fr)) + return 1; + mem += views_get_mem(ct->views); + mem += respip_set_get_mem(ct->respip_set); + mem += auth_zones_get_mem(ct->auth_zones); + mem += forwards_get_mem(ct->fwds); + mem += hints_get_mem(ct->hints); + mem += local_zones_get_mem(ct->local_zones); + mem += acl_list_get_mem(ct->acl); + mem += acl_list_get_mem(ct->acl_interface); + mem += tcl_list_get_mem(ct->tcl); + mem += edns_strings_get_mem(ct->edns_strings); + mem += anchors_get_mem(ct->anchors); + mem += sizeof(*ct->oldcfg); + mem += config_file_getmem(newcfg); + + if(!fr_output_printf(fr, "memory use %d bytes\n", (int)mem)) + return 0; + fr_send_notification(fr, fast_reload_notification_printout); + + return 1; +} + +/** fast reload thread, setup the acl_interface for the ports that + * the server has. */ +static int +ct_acl_interface_setup_ports(struct acl_list* acl_interface, + struct daemon* daemon) +{ + /* clean acl_interface */ + acl_interface_init(acl_interface); + if(!setup_acl_for_ports(acl_interface, daemon->ports[0])) + return 0; + if(daemon->reuseport) { + size_t i; + for(i=1; inum_ports; i++) { + if(!setup_acl_for_ports(acl_interface, + daemon->ports[i])) + return 0; + } + } + return 1; +} + +/** fast reload, add new change to list of auth zones */ +static int +fr_add_auth_zone_change(struct fast_reload_thread* fr, struct auth_zone* old_z, + struct auth_zone* new_z, int is_deleted, int is_added, int is_changed) +{ + struct fast_reload_auth_change* item; + item = calloc(1, sizeof(*item)); + if(!item) { + log_err("malloc failure in add auth zone change"); + return 0; + } + item->old_z = old_z; + item->new_z = new_z; + item->is_deleted = is_deleted; + item->is_added = is_added; + item->is_changed = is_changed; + + item->next = fr->auth_zone_change_list; + fr->auth_zone_change_list = item; + return 1; +} + +/** See if auth master is equal */ +static int +xfr_auth_master_equal(struct auth_master* m1, struct auth_master* m2) +{ + if(!m1 && !m2) + return 1; + if(!m1 || !m2) + return 0; + + if((m1->host && !m2->host) || (!m1->host && m2->host)) + return 0; + if(m1->host && m2->host && strcmp(m1->host, m2->host) != 0) + return 0; + + if((m1->file && !m2->file) || (!m1->file && m2->file)) + return 0; + if(m1->file && m2->file && strcmp(m1->file, m2->file) != 0) + return 0; + + if((m1->http && !m2->http) || (!m1->http && m2->http)) + return 0; + if((m1->ixfr && !m2->ixfr) || (!m1->ixfr && m2->ixfr)) + return 0; + if((m1->allow_notify && !m2->allow_notify) || (!m1->allow_notify && m2->allow_notify)) + return 0; + if((m1->ssl && !m2->ssl) || (!m1->ssl && m2->ssl)) + return 0; + if(m1->port != m2->port) + return 0; + return 1; +} + +/** See if list of auth masters is equal */ +static int +xfr_masterlist_equal(struct auth_master* list1, struct auth_master* list2) +{ + struct auth_master* p1 = list1, *p2 = list2; + while(p1 && p2) { + if(!xfr_auth_master_equal(p1, p2)) + return 0; + p1 = p1->next; + p2 = p2->next; + } + if(!p1 && !p2) + return 1; + return 0; +} + +/** See if the list of masters has changed. */ +static int +xfr_masters_equal(struct auth_xfer* xfr1, struct auth_xfer* xfr2) +{ + if(xfr1 == NULL && xfr2 == NULL) + return 1; + if(xfr1 == NULL && xfr2 != NULL) + return 0; + if(xfr1 != NULL && xfr2 == NULL) + return 0; + if(xfr_masterlist_equal(xfr1->task_probe->masters, + xfr2->task_probe->masters) && + xfr_masterlist_equal(xfr1->task_transfer->masters, + xfr2->task_transfer->masters)) + return 1; + return 0; +} + +/** Check what has changed in auth zones, like added and deleted zones */ +static int +auth_zones_check_changes(struct fast_reload_thread* fr, + struct fast_reload_construct* ct) +{ + /* Check every zone in turn. */ + struct auth_zone* new_z, *old_z; + struct module_env* env = &fr->worker->env; + + fr->old_auth_zones = ct->auth_zones; + /* Nobody is using the new ct version yet. + * Also the ct lock is picked up before the env lock for auth_zones. */ + lock_rw_rdlock(&ct->auth_zones->lock); + + /* Find deleted zones by looping over the current list and looking + * up in the new tree. */ + lock_rw_rdlock(&env->auth_zones->lock); + RBTREE_FOR(old_z, struct auth_zone*, &env->auth_zones->ztree) { + new_z = auth_zone_find(ct->auth_zones, old_z->name, + old_z->namelen, old_z->dclass); + if(!new_z) { + /* The zone has been removed. */ + if(!fr_add_auth_zone_change(fr, old_z, NULL, 1, 0, + 0)) { + lock_rw_unlock(&env->auth_zones->lock); + lock_rw_unlock(&ct->auth_zones->lock); + return 0; + } + } + } + lock_rw_unlock(&env->auth_zones->lock); + + /* Find added zones by looping over new list and lookup in current. */ + RBTREE_FOR(new_z, struct auth_zone*, &ct->auth_zones->ztree) { + lock_rw_rdlock(&env->auth_zones->lock); + old_z = auth_zone_find(env->auth_zones, new_z->name, + new_z->namelen, new_z->dclass); + if(!old_z) { + /* The zone has been added. */ + lock_rw_unlock(&env->auth_zones->lock); + if(!fr_add_auth_zone_change(fr, NULL, new_z, 0, 1, + 0)) { + lock_rw_unlock(&ct->auth_zones->lock); + return 0; + } + } else { + uint32_t old_serial = 0, new_serial = 0; + int have_old = 0, have_new = 0; + struct auth_xfer* old_xfr, *new_xfr; + lock_rw_rdlock(&new_z->lock); + lock_rw_rdlock(&old_z->lock); + new_xfr = auth_xfer_find(ct->auth_zones, new_z->name, + new_z->namelen, new_z->dclass); + old_xfr = auth_xfer_find(env->auth_zones, old_z->name, + old_z->namelen, old_z->dclass); + if(new_xfr) { + lock_basic_lock(&new_xfr->lock); + } + if(old_xfr) { + lock_basic_lock(&old_xfr->lock); + } + lock_rw_unlock(&env->auth_zones->lock); + + /* Change in the auth zone can be detected. */ + /* A change in serial number means that auth_xfer + * has to be updated. */ + have_old = (auth_zone_get_serial(old_z, + &old_serial)!=0); + have_new = (auth_zone_get_serial(new_z, + &new_serial)!=0); + if(have_old != have_new || old_serial != new_serial + || !xfr_masters_equal(old_xfr, new_xfr)) { + /* The zone has been changed. */ + if(!fr_add_auth_zone_change(fr, old_z, new_z, + 0, 0, 1)) { + lock_rw_unlock(&old_z->lock); + lock_rw_unlock(&new_z->lock); + lock_rw_unlock(&ct->auth_zones->lock); + if(new_xfr) { + lock_basic_unlock(&new_xfr->lock); + } + if(old_xfr) { + lock_basic_unlock(&old_xfr->lock); + } + return 0; + } + } + + if(new_xfr) { + lock_basic_unlock(&new_xfr->lock); + } + if(old_xfr) { + lock_basic_unlock(&old_xfr->lock); + } + lock_rw_unlock(&old_z->lock); + lock_rw_unlock(&new_z->lock); + } + } + + lock_rw_unlock(&ct->auth_zones->lock); + return 1; +} + +/** fast reload thread, construct from config the new items */ +static int +fr_construct_from_config(struct fast_reload_thread* fr, + struct config_file* newcfg, struct fast_reload_construct* ct) +{ + int have_view_respip_cfg = 0; + + if(!(ct->views = views_create())) { + fr_construct_clear(ct); + return 0; + } + if(!views_apply_cfg(ct->views, newcfg)) { + fr_construct_clear(ct); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + + if(!(ct->acl = acl_list_create())) { + fr_construct_clear(ct); + return 0; + } + if(!acl_list_apply_cfg(ct->acl, newcfg, ct->views)) { + fr_construct_clear(ct); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + + if(!(ct->acl_interface = acl_list_create())) { + fr_construct_clear(ct); + return 0; + } + if(!ct_acl_interface_setup_ports(ct->acl_interface, + fr->worker->daemon)) { + fr_construct_clear(ct); + return 0; + } + if(!acl_interface_apply_cfg(ct->acl_interface, newcfg, ct->views)) { + fr_construct_clear(ct); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + + if(!(ct->tcl = tcl_list_create())) { + fr_construct_clear(ct); + return 0; + } + if(!tcl_list_apply_cfg(ct->tcl, newcfg)) { + fr_construct_clear(ct); + return 0; + } + if(fr->worker->daemon->tcl->tree.count != 0) + fr->worker->daemon->fast_reload_tcl_has_changes = 1; + else fr->worker->daemon->fast_reload_tcl_has_changes = 0; + if(fr_poll_for_quit(fr)) + return 1; + + if(!(ct->auth_zones = auth_zones_create())) { + fr_construct_clear(ct); + return 0; + } + if(!auth_zones_apply_cfg(ct->auth_zones, newcfg, 1, &ct->use_rpz, + fr->worker->daemon->env, &fr->worker->daemon->mods)) { + fr_construct_clear(ct); + return 0; + } + if(!auth_zones_check_changes(fr, ct)) { + fr_construct_clear(ct); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + + if(!(ct->fwds = forwards_create())) { + fr_construct_clear(ct); + return 0; + } + if(!forwards_apply_cfg(ct->fwds, newcfg)) { + fr_construct_clear(ct); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + + if(!(ct->hints = hints_create())) { + fr_construct_clear(ct); + return 0; + } + if(!hints_apply_cfg(ct->hints, newcfg)) { + fr_construct_clear(ct); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + + if(!(ct->local_zones = local_zones_create())) { + fr_construct_clear(ct); + return 0; + } + if(!local_zones_apply_cfg(ct->local_zones, newcfg)) { + fr_construct_clear(ct); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + + if(!(ct->respip_set = respip_set_create())) { + fr_construct_clear(ct); + return 0; + } + if(!respip_global_apply_cfg(ct->respip_set, newcfg)) { + fr_construct_clear(ct); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + if(!respip_views_apply_cfg(ct->views, newcfg, &have_view_respip_cfg)) { + fr_construct_clear(ct); + return 0; + } + ct->use_response_ip = !respip_set_is_empty(ct->respip_set) || + have_view_respip_cfg; + if(fr_poll_for_quit(fr)) + return 1; + + if(!(ct->edns_strings = edns_strings_create())) { + fr_construct_clear(ct); + return 0; + } + if(!edns_strings_apply_cfg(ct->edns_strings, newcfg)) { + fr_construct_clear(ct); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + + if(fr->worker->env.anchors) { + /* There are trust anchors already, so create it for reload. */ + if(!(ct->anchors = anchors_create())) { + fr_construct_clear(ct); + return 0; + } + if(!anchors_apply_cfg(ct->anchors, newcfg)) { + fr_construct_clear(ct); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + } + + if(!val_env_parse_key_iter(newcfg->val_nsec3_key_iterations, + &ct->nsec3_keysize, &ct->nsec3_maxiter, + &ct->nsec3_keyiter_count)) { + fr_construct_clear(ct); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + + if(!read_fetch_policy(&ct->target_fetch_policy, + &ct->max_dependency_depth, newcfg->target_fetch_policy)) { + fr_construct_clear(ct); + return 0; + } + if(!(ct->donotq = donotq_create())) { + fr_construct_clear(ct); + return 0; + } + if(!donotq_apply_cfg(ct->donotq, newcfg)) { + fr_construct_clear(ct); + return 0; + } + if(!(ct->priv = priv_create())) { + fr_construct_clear(ct); + return 0; + } + if(!priv_apply_cfg(ct->priv, newcfg)) { + fr_construct_clear(ct); + return 0; + } + if(newcfg->caps_whitelist) { + if(!(ct->caps_white = caps_white_create())) { + fr_construct_clear(ct); + return 0; + } + if(!caps_white_apply_cfg(ct->caps_white, newcfg)) { + fr_construct_clear(ct); + return 0; + } + } + if(!nat64_apply_cfg(&ct->nat64, newcfg)) { + fr_construct_clear(ct); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + + if(!setup_wait_limits(&ct->wait_limits_netblock, + &ct->wait_limits_cookie_netblock, newcfg)) { + fr_construct_clear(ct); + return 0; + } + if(!setup_domain_limits(&ct->domain_limits, newcfg)) { + fr_construct_clear(ct); + return 0; + } + if(fr_poll_for_quit(fr)) + return 1; + + if(!(ct->oldcfg = (struct config_file*)calloc(1, + sizeof(*ct->oldcfg)))) { + fr_construct_clear(ct); + log_err("out of memory"); + return 0; + } + if(fr->fr_verb >= 2) { + if(!fr_printmem(fr, newcfg, ct)) + return 0; + } + return 1; +} + +/** fast reload thread, finish timers */ +static int +fr_finish_time(struct fast_reload_thread* fr, struct timeval* time_start, + struct timeval* time_read, struct timeval* time_construct, + struct timeval* time_reload, struct timeval* time_end) +{ + struct timeval total, readtime, constructtime, reloadtime, deletetime; + if(gettimeofday(time_end, NULL) < 0) + log_err("gettimeofday: %s", strerror(errno)); + + timeval_subtract(&total, time_end, time_start); + timeval_subtract(&readtime, time_read, time_start); + timeval_subtract(&constructtime, time_construct, time_read); + timeval_subtract(&reloadtime, time_reload, time_construct); + timeval_subtract(&deletetime, time_end, time_reload); + if(!fr_output_printf(fr, "read disk %3d.%6.6ds\n", + (int)readtime.tv_sec, (int)readtime.tv_usec)) + return 0; + if(!fr_output_printf(fr, "construct %3d.%6.6ds\n", + (int)constructtime.tv_sec, (int)constructtime.tv_usec)) + return 0; + if(!fr_output_printf(fr, "reload %3d.%6.6ds\n", + (int)reloadtime.tv_sec, (int)reloadtime.tv_usec)) + return 0; + if(!fr_output_printf(fr, "deletes %3d.%6.6ds\n", + (int)deletetime.tv_sec, (int)deletetime.tv_usec)) + return 0; + if(!fr_output_printf(fr, "total time %3d.%6.6ds\n", (int)total.tv_sec, + (int)total.tv_usec)) + return 0; + fr_send_notification(fr, fast_reload_notification_printout); + return 1; +} + +/** Swap auth zone information */ +static void +auth_zones_swap(struct auth_zones* az, struct auth_zones* data) +{ + rbtree_type oldztree = az->ztree; + int old_have_downstream = az->have_downstream; + struct auth_zone* old_rpz_first = az->rpz_first; + + az->ztree = data->ztree; + data->ztree = oldztree; + + az->have_downstream = data->have_downstream; + data->have_downstream = old_have_downstream; + + /* Leave num_query_up and num_query_down, the statistics can + * remain counted. */ + + az->rpz_first = data->rpz_first; + data->rpz_first = old_rpz_first; + + /* The xtree is not swapped. This contains the auth_xfer elements + * that contain tasks in progress, like zone transfers. + * The unchanged zones can keep their tasks in the tree, and thus + * the xfer elements can continue to be their callbacks. */ +} + +#ifdef ATOMIC_POINTER_LOCK_FREE +/** Fast reload thread, if atomics are available, copy the config items + * one by one with atomic store operations. */ +static void +fr_atomic_copy_cfg(struct config_file* oldcfg, struct config_file* cfg, + struct config_file* newcfg) +{ +#define COPY_VAR_int(var) oldcfg->var = cfg->var; atomic_store((_Atomic int*)&cfg->var, newcfg->var); newcfg->var = 0; +#define COPY_VAR_ptr(var) oldcfg->var = cfg->var; atomic_store((void* _Atomic*)&cfg->var, newcfg->var); newcfg->var = 0; +#define COPY_VAR_unsigned_int(var) oldcfg->var = cfg->var; atomic_store((_Atomic unsigned*)&cfg->var, newcfg->var); newcfg->var = 0; +#define COPY_VAR_size_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic size_t*)&cfg->var, newcfg->var); newcfg->var = 0; +#define COPY_VAR_uint8_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic uint8_t*)&cfg->var, newcfg->var); newcfg->var = 0; +#define COPY_VAR_uint16_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic uint16_t*)&cfg->var, newcfg->var); newcfg->var = 0; +#define COPY_VAR_uint32_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic uint32_t*)&cfg->var, newcfg->var); newcfg->var = 0; +#define COPY_VAR_int32_t(var) oldcfg->var = cfg->var; atomic_store((_Atomic int32_t*)&cfg->var, newcfg->var); newcfg->var = 0; + /* If config file items are missing from this list, they are + * not updated by fast-reload +p. */ + /* For missing items, the oldcfg item is not updated, still NULL, + * and the cfg stays the same. The newcfg item is untouched. + * The newcfg item is then deleted later. */ + /* Items that need synchronisation are omitted from the list. + * Use fast-reload without +p to update them together. */ + COPY_VAR_int(verbosity); + COPY_VAR_int(stat_interval); + COPY_VAR_int(stat_cumulative); + COPY_VAR_int(stat_extended); + COPY_VAR_int(stat_inhibit_zero); + COPY_VAR_int(num_threads); + COPY_VAR_int(port); + COPY_VAR_int(do_ip4); + COPY_VAR_int(do_ip6); + COPY_VAR_int(do_nat64); + COPY_VAR_int(prefer_ip4); + COPY_VAR_int(prefer_ip6); + COPY_VAR_int(do_udp); + COPY_VAR_int(do_tcp); + COPY_VAR_size_t(max_reuse_tcp_queries); + COPY_VAR_int(tcp_reuse_timeout); + COPY_VAR_int(tcp_auth_query_timeout); + COPY_VAR_int(tcp_upstream); + COPY_VAR_int(udp_upstream_without_downstream); + COPY_VAR_int(tcp_mss); + COPY_VAR_int(outgoing_tcp_mss); + COPY_VAR_int(tcp_idle_timeout); + COPY_VAR_int(do_tcp_keepalive); + COPY_VAR_int(tcp_keepalive_timeout); + COPY_VAR_int(sock_queue_timeout); + COPY_VAR_ptr(proxy_protocol_port); + COPY_VAR_ptr(ssl_service_key); + COPY_VAR_ptr(ssl_service_pem); + COPY_VAR_int(ssl_port); + COPY_VAR_int(ssl_upstream); + COPY_VAR_ptr(tls_cert_bundle); + COPY_VAR_int(tls_win_cert); + COPY_VAR_ptr(tls_additional_port); + /* The first is used to walk throught the list but last is + * only used during config read. */ + COPY_VAR_ptr(tls_session_ticket_keys.first); + COPY_VAR_ptr(tls_session_ticket_keys.last); + COPY_VAR_ptr(tls_ciphers); + COPY_VAR_ptr(tls_ciphersuites); + COPY_VAR_int(tls_use_sni); + COPY_VAR_int(https_port); + COPY_VAR_ptr(http_endpoint); + COPY_VAR_uint32_t(http_max_streams); + COPY_VAR_size_t(http_query_buffer_size); + COPY_VAR_size_t(http_response_buffer_size); + COPY_VAR_int(http_nodelay); + COPY_VAR_int(http_notls_downstream); + COPY_VAR_int(outgoing_num_ports); + COPY_VAR_size_t(outgoing_num_tcp); + COPY_VAR_size_t(incoming_num_tcp); + COPY_VAR_ptr(outgoing_avail_ports); + COPY_VAR_size_t(edns_buffer_size); + COPY_VAR_size_t(stream_wait_size); + COPY_VAR_size_t(msg_buffer_size); + COPY_VAR_size_t(msg_cache_size); + COPY_VAR_size_t(msg_cache_slabs); + COPY_VAR_size_t(num_queries_per_thread); + COPY_VAR_size_t(jostle_time); + COPY_VAR_size_t(rrset_cache_size); + COPY_VAR_size_t(rrset_cache_slabs); + COPY_VAR_int(host_ttl); + COPY_VAR_size_t(infra_cache_slabs); + COPY_VAR_size_t(infra_cache_numhosts); + COPY_VAR_int(infra_cache_min_rtt); + COPY_VAR_int(infra_cache_max_rtt); + COPY_VAR_int(infra_keep_probing); + COPY_VAR_int(delay_close); + COPY_VAR_int(udp_connect); + COPY_VAR_ptr(target_fetch_policy); + COPY_VAR_int(fast_server_permil); + COPY_VAR_size_t(fast_server_num); + COPY_VAR_int(if_automatic); + COPY_VAR_ptr(if_automatic_ports); + COPY_VAR_size_t(so_rcvbuf); + COPY_VAR_size_t(so_sndbuf); + COPY_VAR_int(so_reuseport); + COPY_VAR_int(ip_transparent); + COPY_VAR_int(ip_freebind); + COPY_VAR_int(ip_dscp); + /* Not copied because the length and items could then not match. + num_ifs, ifs, num_out_ifs, out_ifs + */ + COPY_VAR_ptr(root_hints); + COPY_VAR_ptr(stubs); + COPY_VAR_ptr(forwards); + COPY_VAR_ptr(auths); + COPY_VAR_ptr(views); + COPY_VAR_ptr(donotqueryaddrs); +#ifdef CLIENT_SUBNET + COPY_VAR_ptr(client_subnet); + COPY_VAR_ptr(client_subnet_zone); + COPY_VAR_uint16_t(client_subnet_opcode); + COPY_VAR_int(client_subnet_always_forward); + COPY_VAR_uint8_t(max_client_subnet_ipv4); + COPY_VAR_uint8_t(max_client_subnet_ipv6); + COPY_VAR_uint8_t(min_client_subnet_ipv4); + COPY_VAR_uint8_t(min_client_subnet_ipv6); + COPY_VAR_uint32_t(max_ecs_tree_size_ipv4); + COPY_VAR_uint32_t(max_ecs_tree_size_ipv6); +#endif + COPY_VAR_ptr(acls); + COPY_VAR_int(donotquery_localhost); + COPY_VAR_ptr(tcp_connection_limits); + COPY_VAR_int(harden_short_bufsize); + COPY_VAR_int(harden_large_queries); + COPY_VAR_int(harden_glue); + COPY_VAR_int(harden_dnssec_stripped); + COPY_VAR_int(harden_below_nxdomain); + COPY_VAR_int(harden_referral_path); + COPY_VAR_int(harden_algo_downgrade); + COPY_VAR_int(harden_unknown_additional); + COPY_VAR_int(use_caps_bits_for_id); + COPY_VAR_ptr(caps_whitelist); + COPY_VAR_ptr(private_address); + COPY_VAR_ptr(private_domain); + COPY_VAR_size_t(unwanted_threshold); + COPY_VAR_int(max_ttl); + COPY_VAR_int(min_ttl); + COPY_VAR_int(max_negative_ttl); + COPY_VAR_int(min_negative_ttl); + COPY_VAR_int(prefetch); + COPY_VAR_int(prefetch_key); + COPY_VAR_int(deny_any); + COPY_VAR_ptr(chrootdir); + COPY_VAR_ptr(username); + COPY_VAR_ptr(directory); + COPY_VAR_ptr(logfile); + COPY_VAR_ptr(pidfile); + COPY_VAR_int(use_syslog); + COPY_VAR_int(log_time_ascii); + COPY_VAR_int(log_queries); + COPY_VAR_int(log_replies); + COPY_VAR_int(log_tag_queryreply); + COPY_VAR_int(log_local_actions); + COPY_VAR_int(log_servfail); + COPY_VAR_ptr(log_identity); + COPY_VAR_int(log_destaddr); + COPY_VAR_int(hide_identity); + COPY_VAR_int(hide_version); + COPY_VAR_int(hide_trustanchor); + COPY_VAR_int(hide_http_user_agent); + COPY_VAR_ptr(identity); + COPY_VAR_ptr(version); + COPY_VAR_ptr(http_user_agent); + COPY_VAR_ptr(nsid_cfg_str); + /* Not copied because the length and items could then not match. + nsid; + nsid_len; + */ + COPY_VAR_ptr(module_conf); + COPY_VAR_ptr(trust_anchor_file_list); + COPY_VAR_ptr(trust_anchor_list); + COPY_VAR_ptr(auto_trust_anchor_file_list); + COPY_VAR_ptr(trusted_keys_file_list); + COPY_VAR_ptr(domain_insecure); + COPY_VAR_int(trust_anchor_signaling); + COPY_VAR_int(root_key_sentinel); + COPY_VAR_int32_t(val_date_override); + COPY_VAR_int32_t(val_sig_skew_min); + COPY_VAR_int32_t(val_sig_skew_max); + COPY_VAR_int32_t(val_max_restart); + COPY_VAR_int(bogus_ttl); + COPY_VAR_int(val_clean_additional); + COPY_VAR_int(val_log_level); + COPY_VAR_int(val_log_squelch); + COPY_VAR_int(val_permissive_mode); + COPY_VAR_int(aggressive_nsec); + COPY_VAR_int(ignore_cd); + COPY_VAR_int(disable_edns_do); + COPY_VAR_int(serve_expired); + COPY_VAR_int(serve_expired_ttl); + COPY_VAR_int(serve_expired_ttl_reset); + COPY_VAR_int(serve_expired_reply_ttl); + COPY_VAR_int(serve_expired_client_timeout); + COPY_VAR_int(ede_serve_expired); + COPY_VAR_int(serve_original_ttl); + COPY_VAR_ptr(val_nsec3_key_iterations); + COPY_VAR_int(zonemd_permissive_mode); + COPY_VAR_unsigned_int(add_holddown); + COPY_VAR_unsigned_int(del_holddown); + COPY_VAR_unsigned_int(keep_missing); + COPY_VAR_int(permit_small_holddown); + COPY_VAR_size_t(key_cache_size); + COPY_VAR_size_t(key_cache_slabs); + COPY_VAR_size_t(neg_cache_size); + COPY_VAR_ptr(local_zones); + COPY_VAR_ptr(local_zones_nodefault); +#ifdef USE_IPSET + COPY_VAR_ptr(local_zones_ipset); +#endif + COPY_VAR_int(local_zones_disable_default); + COPY_VAR_ptr(local_data); + COPY_VAR_ptr(local_zone_overrides); + COPY_VAR_int(unblock_lan_zones); + COPY_VAR_int(insecure_lan_zones); + /* These reference tags + COPY_VAR_ptr(local_zone_tags); + COPY_VAR_ptr(acl_tags); + COPY_VAR_ptr(acl_tag_actions); + COPY_VAR_ptr(acl_tag_datas); + */ + COPY_VAR_ptr(acl_view); + COPY_VAR_ptr(interface_actions); + /* These reference tags + COPY_VAR_ptr(interface_tags); + COPY_VAR_ptr(interface_tag_actions); + COPY_VAR_ptr(interface_tag_datas); + */ + COPY_VAR_ptr(interface_view); + /* This references tags + COPY_VAR_ptr(respip_tags); + */ + COPY_VAR_ptr(respip_actions); + COPY_VAR_ptr(respip_data); + /* Not copied because the length and items could then not match. + * also the respip module keeps a pointer to the array in its state. + tagname, num_tags + */ + COPY_VAR_int(remote_control_enable); + /* The first is used to walk throught the list but last is + * only used during config read. */ + COPY_VAR_ptr(control_ifs.first); + COPY_VAR_ptr(control_ifs.last); + COPY_VAR_int(control_use_cert); + COPY_VAR_int(control_port); + COPY_VAR_ptr(server_key_file); + COPY_VAR_ptr(server_cert_file); + COPY_VAR_ptr(control_key_file); + COPY_VAR_ptr(control_cert_file); + COPY_VAR_ptr(python_script); + COPY_VAR_ptr(dynlib_file); + COPY_VAR_int(use_systemd); + COPY_VAR_int(do_daemonize); + COPY_VAR_int(minimal_responses); + COPY_VAR_int(rrset_roundrobin); + COPY_VAR_int(unknown_server_time_limit); + COPY_VAR_int(discard_timeout); + COPY_VAR_int(wait_limit); + COPY_VAR_int(wait_limit_cookie); + COPY_VAR_ptr(wait_limit_netblock); + COPY_VAR_ptr(wait_limit_cookie_netblock); + COPY_VAR_size_t(max_udp_size); + COPY_VAR_ptr(dns64_prefix); + COPY_VAR_int(dns64_synthall); + COPY_VAR_ptr(dns64_ignore_aaaa); + COPY_VAR_ptr(nat64_prefix); + COPY_VAR_int(dnstap); + COPY_VAR_int(dnstap_bidirectional); + COPY_VAR_ptr(dnstap_socket_path); + COPY_VAR_ptr(dnstap_ip); + COPY_VAR_int(dnstap_tls); + COPY_VAR_ptr(dnstap_tls_server_name); + COPY_VAR_ptr(dnstap_tls_cert_bundle); + COPY_VAR_ptr(dnstap_tls_client_key_file); + COPY_VAR_ptr(dnstap_tls_client_cert_file); + COPY_VAR_int(dnstap_send_identity); + COPY_VAR_int(dnstap_send_version); + COPY_VAR_ptr(dnstap_identity); + COPY_VAR_ptr(dnstap_version); + COPY_VAR_int(dnstap_sample_rate); + COPY_VAR_int(dnstap_log_resolver_query_messages); + COPY_VAR_int(dnstap_log_resolver_response_messages); + COPY_VAR_int(dnstap_log_client_query_messages); + COPY_VAR_int(dnstap_log_client_response_messages); + COPY_VAR_int(dnstap_log_forwarder_query_messages); + COPY_VAR_int(dnstap_log_forwarder_response_messages); + COPY_VAR_int(disable_dnssec_lame_check); + COPY_VAR_int(ip_ratelimit); + COPY_VAR_int(ip_ratelimit_cookie); + COPY_VAR_size_t(ip_ratelimit_slabs); + COPY_VAR_size_t(ip_ratelimit_size); + COPY_VAR_int(ip_ratelimit_factor); + COPY_VAR_int(ip_ratelimit_backoff); + COPY_VAR_int(ratelimit); + COPY_VAR_size_t(ratelimit_slabs); + COPY_VAR_size_t(ratelimit_size); + COPY_VAR_ptr(ratelimit_for_domain); + COPY_VAR_ptr(ratelimit_below_domain); + COPY_VAR_int(ratelimit_factor); + COPY_VAR_int(ratelimit_backoff); + COPY_VAR_int(outbound_msg_retry); + COPY_VAR_int(max_sent_count); + COPY_VAR_int(max_query_restarts); + COPY_VAR_int(qname_minimisation); + COPY_VAR_int(qname_minimisation_strict); + COPY_VAR_int(shm_enable); + COPY_VAR_int(shm_key); + COPY_VAR_ptr(edns_client_strings); + COPY_VAR_uint16_t(edns_client_string_opcode); + COPY_VAR_int(dnscrypt); + COPY_VAR_int(dnscrypt_port); + COPY_VAR_ptr(dnscrypt_provider); + COPY_VAR_ptr(dnscrypt_secret_key); + COPY_VAR_ptr(dnscrypt_provider_cert); + COPY_VAR_ptr(dnscrypt_provider_cert_rotated); + COPY_VAR_size_t(dnscrypt_shared_secret_cache_size); + COPY_VAR_size_t(dnscrypt_shared_secret_cache_slabs); + COPY_VAR_size_t(dnscrypt_nonce_cache_size); + COPY_VAR_size_t(dnscrypt_nonce_cache_slabs); + COPY_VAR_int(pad_responses); + COPY_VAR_size_t(pad_responses_block_size); + COPY_VAR_int(pad_queries); + COPY_VAR_size_t(pad_queries_block_size); +#ifdef USE_IPSECMOD + COPY_VAR_int(ipsecmod_enabled); + COPY_VAR_ptr(ipsecmod_whitelist); + COPY_VAR_ptr(ipsecmod_hook); + COPY_VAR_int(ipsecmod_ignore_bogus); + COPY_VAR_int(ipsecmod_max_ttl); + COPY_VAR_int(ipsecmod_strict); +#endif +#ifdef USE_CACHEDB + COPY_VAR_ptr(cachedb_backend); + COPY_VAR_ptr(cachedb_secret); + COPY_VAR_int(cachedb_no_store); + COPY_VAR_int(cachedb_check_when_serve_expired); +#ifdef USE_REDIS + COPY_VAR_ptr(redis_server_host); + COPY_VAR_int(redis_server_port); + COPY_VAR_ptr(redis_server_path); + COPY_VAR_ptr(redis_server_password); + COPY_VAR_int(redis_timeout); + COPY_VAR_int(redis_expire_records); + COPY_VAR_int(redis_logical_db); +#endif +#endif + COPY_VAR_int(do_answer_cookie); + /* Not copied because the length and content could then not match. + cookie_secret[40], cookie_secret_len + */ +#ifdef USE_IPSET + COPY_VAR_ptr(ipset_name_v4); + COPY_VAR_ptr(ipset_name_v6); +#endif + COPY_VAR_int(ede); +} +#endif /* ATOMIC_POINTER_LOCK_FREE */ + +/** fast reload thread, adjust the cache sizes */ +static void +fr_adjust_cache(struct module_env* env, struct config_file* oldcfg) +{ + if(env->cfg->msg_cache_size != oldcfg->msg_cache_size) + slabhash_adjust_size(env->msg_cache, env->cfg->msg_cache_size); + if(env->cfg->rrset_cache_size != oldcfg->rrset_cache_size) + slabhash_adjust_size(&env->rrset_cache->table, + env->cfg->rrset_cache_size); + if(env->key_cache && + env->cfg->key_cache_size != oldcfg->key_cache_size) + slabhash_adjust_size(env->key_cache->slab, + env->cfg->key_cache_size); + if(env->cfg->infra_cache_numhosts != oldcfg->infra_cache_numhosts) { + size_t inframem = env->cfg->infra_cache_numhosts * + (sizeof(struct infra_key) + sizeof(struct infra_data) + + INFRA_BYTES_NAME); + slabhash_adjust_size(env->infra_cache->hosts, inframem); + } + if(env->cfg->ratelimit_size != oldcfg->ratelimit_size) { + slabhash_adjust_size(env->infra_cache->domain_rates, + env->cfg->ratelimit_size); + slabhash_adjust_size(env->infra_cache->client_ip_rates, + env->cfg->ratelimit_size); + } + if(env->neg_cache && + env->cfg->neg_cache_size != oldcfg->neg_cache_size) { + val_neg_adjust_size(env->neg_cache, env->cfg->neg_cache_size); + } +} + +/** fast reload thread, adjust the iterator env */ +static void +fr_adjust_iter_env(struct module_env* env, struct fast_reload_construct* ct) +{ + int m; + struct iter_env* iter_env = NULL; + /* There is no comparison here to see if no options changed and thus + * no swap is needed, the trees with addresses and domains can be + * large and that would take too long. Instead the trees are + * swapped in. */ + + /* Because the iterator env is not locked, the update cannot happen + * when fr nopause is used. Without it the fast reload pauses the + * other threads, so they are not currently using the structure. */ + m = modstack_find(env->modstack, "iterator"); + if(m != -1) iter_env = (struct iter_env*)env->modinfo[m]; + if(iter_env) { + /* Swap the data so that the delete happens afterwards. */ + int* oldtargetfetchpolicy = iter_env->target_fetch_policy; + int oldmaxdependencydepth = iter_env->max_dependency_depth; + struct iter_donotq* olddonotq = iter_env->donotq; + struct iter_priv* oldpriv = iter_env->priv; + struct rbtree_type* oldcapswhite = iter_env->caps_white; + struct iter_nat64 oldnat64 = iter_env->nat64; + + iter_env->target_fetch_policy = ct->target_fetch_policy; + iter_env->max_dependency_depth = ct->max_dependency_depth; + iter_env->donotq = ct->donotq; + iter_env->priv = ct->priv; + iter_env->caps_white = ct->caps_white; + iter_env->nat64 = ct->nat64; + iter_env->outbound_msg_retry = env->cfg->outbound_msg_retry; + iter_env->max_sent_count = env->cfg->max_sent_count; + iter_env->max_query_restarts = env->cfg->max_query_restarts; + + ct->target_fetch_policy = oldtargetfetchpolicy; + ct->max_dependency_depth = oldmaxdependencydepth; + ct->donotq = olddonotq; + ct->priv = oldpriv; + ct->caps_white = oldcapswhite; + ct->nat64 = oldnat64; + } +} + +/** fast reload thread, adjust the validator env */ +static void +fr_adjust_val_env(struct module_env* env, struct fast_reload_construct* ct, + struct config_file* oldcfg) +{ + int m; + struct val_env* val_env = NULL; + if(env->cfg->bogus_ttl == oldcfg->bogus_ttl && + env->cfg->val_date_override == oldcfg->val_date_override && + env->cfg->val_sig_skew_min == oldcfg->val_sig_skew_min && + env->cfg->val_sig_skew_max == oldcfg->val_sig_skew_max && + env->cfg->val_max_restart == oldcfg->val_max_restart && + strcmp(env->cfg->val_nsec3_key_iterations, + oldcfg->val_nsec3_key_iterations) == 0) + return; /* no changes */ + + /* Because the validator env is not locked, the update cannot happen + * when fr nopause is used. Without it the fast reload pauses the + * other threads, so they are not currently using the structure. */ + m = modstack_find(env->modstack, "validator"); + if(m != -1) val_env = (struct val_env*)env->modinfo[m]; + if(val_env) { + /* Swap the arrays so that the delete happens afterwards. */ + size_t* oldkeysize = val_env->nsec3_keysize; + size_t* oldmaxiter = val_env->nsec3_maxiter; + val_env->nsec3_keysize = NULL; + val_env->nsec3_maxiter = NULL; + val_env_apply_cfg(val_env, env->cfg, ct->nsec3_keysize, + ct->nsec3_maxiter, ct->nsec3_keyiter_count); + ct->nsec3_keysize = oldkeysize; + ct->nsec3_maxiter = oldmaxiter; + if(env->neg_cache) { + lock_basic_lock(&env->neg_cache->lock); + env->neg_cache->nsec3_max_iter = val_env-> + nsec3_maxiter[val_env->nsec3_keyiter_count-1]; + lock_basic_unlock(&env->neg_cache->lock); + } + } +} + +/** fast reload thread, adjust the infra cache parameters */ +static void +fr_adjust_infra(struct module_env* env, struct fast_reload_construct* ct) +{ + struct infra_cache* infra = env->infra_cache; + struct config_file* cfg = env->cfg; + struct rbtree_type oldwaitlim = infra->wait_limits_netblock; + struct rbtree_type oldwaitlimcookie = + infra->wait_limits_cookie_netblock; + struct rbtree_type olddomainlim = infra->domain_limits; + + /* The size of the infra cache and ip rates is changed + * in fr_adjust_cache. */ + infra->host_ttl = cfg->host_ttl; + infra->infra_keep_probing = cfg->infra_keep_probing; + infra_dp_ratelimit = cfg->ratelimit; + infra_ip_ratelimit = cfg->ip_ratelimit; + infra_ip_ratelimit_cookie = cfg->ip_ratelimit_cookie; + infra->wait_limits_netblock = ct->wait_limits_netblock; + infra->wait_limits_cookie_netblock = ct->wait_limits_cookie_netblock; + infra->domain_limits = ct->domain_limits; + + ct->wait_limits_netblock = oldwaitlim; + ct->wait_limits_cookie_netblock = oldwaitlimcookie; + ct->domain_limits = olddomainlim; +} + +/** fast reload thread, reload config with putting the new config items + * in place and swapping out the old items. */ +static int +fr_reload_config(struct fast_reload_thread* fr, struct config_file* newcfg, + struct fast_reload_construct* ct) +{ + struct daemon* daemon = fr->worker->daemon; + struct module_env* env = daemon->env; + + /* These are constructed in the fr_construct_from_config routine. */ + log_assert(ct->oldcfg); + log_assert(ct->fwds); + log_assert(ct->hints); + + /* Grab big locks to satisfy lock conditions. */ + lock_rw_wrlock(&ct->views->lock); + lock_rw_wrlock(&env->views->lock); + lock_rw_wrlock(&ct->respip_set->lock); + lock_rw_wrlock(&env->respip_set->lock); + lock_rw_wrlock(&ct->local_zones->lock); + lock_rw_wrlock(&daemon->local_zones->lock); + lock_rw_wrlock(&ct->auth_zones->rpz_lock); + lock_rw_wrlock(&env->auth_zones->rpz_lock); + lock_rw_wrlock(&ct->auth_zones->lock); + lock_rw_wrlock(&env->auth_zones->lock); + lock_rw_wrlock(&ct->fwds->lock); + lock_rw_wrlock(&env->fwds->lock); + lock_rw_wrlock(&ct->hints->lock); + lock_rw_wrlock(&env->hints->lock); + if(ct->anchors) { + lock_basic_lock(&ct->anchors->lock); + lock_basic_lock(&env->anchors->lock); + } + +#ifdef ATOMIC_POINTER_LOCK_FREE + if(fr->fr_nopause) { + fr_atomic_copy_cfg(ct->oldcfg, env->cfg, newcfg); + } else { +#endif + /* Store old config elements. */ + *ct->oldcfg = *env->cfg; + /* Insert new config elements. */ + *env->cfg = *newcfg; +#ifdef ATOMIC_POINTER_LOCK_FREE + } +#endif + + if(env->cfg->log_identity || ct->oldcfg->log_identity) { + /* pick up new log_identity string to use for log output. */ + log_ident_set_or_default(env->cfg->log_identity); + } + /* the newcfg elements are in env->cfg, so should not be freed here. */ +#ifdef ATOMIC_POINTER_LOCK_FREE + /* if used, the routine that copies the config has zeroed items. */ + if(!fr->fr_nopause) +#endif + memset(newcfg, 0, sizeof(*newcfg)); + + /* Quickly swap the tree roots themselves with the already allocated + * elements. This is a quick swap operation on the pointer. + * The other threads are stopped and locks are held, so that a + * consistent view of the configuration, before, and after, exists + * towards the state machine for query resolution. */ + forwards_swap_tree(env->fwds, ct->fwds); + hints_swap_tree(env->hints, ct->hints); + views_swap_tree(env->views, ct->views); + acl_list_swap_tree(daemon->acl, ct->acl); + acl_list_swap_tree(daemon->acl_interface, ct->acl_interface); + tcl_list_swap_tree(daemon->tcl, ct->tcl); + local_zones_swap_tree(daemon->local_zones, ct->local_zones); + respip_set_swap_tree(env->respip_set, ct->respip_set); + daemon->use_response_ip = ct->use_response_ip; + daemon->use_rpz = ct->use_rpz; + auth_zones_swap(env->auth_zones, ct->auth_zones); + edns_strings_swap_tree(env->edns_strings, ct->edns_strings); + anchors_swap_tree(env->anchors, ct->anchors); +#ifdef USE_CACHEDB + daemon->env->cachedb_enabled = cachedb_is_enabled(&daemon->mods, + daemon->env); +#endif +#ifdef USE_DNSTAP + if(env->cfg->dnstap) { + if(!fr->fr_nopause) + dt_apply_cfg(daemon->dtenv, env->cfg); + else dt_apply_logcfg(daemon->dtenv, env->cfg); + } +#endif + fr_adjust_cache(env, ct->oldcfg); + if(!fr->fr_nopause) { + fr_adjust_iter_env(env, ct); + fr_adjust_val_env(env, ct, ct->oldcfg); + fr_adjust_infra(env, ct); + } + + /* Set globals with new config. */ + config_apply(env->cfg); + + lock_rw_unlock(&ct->views->lock); + lock_rw_unlock(&env->views->lock); + lock_rw_unlock(&ct->respip_set->lock); + lock_rw_unlock(&env->respip_set->lock); + lock_rw_unlock(&ct->local_zones->lock); + lock_rw_unlock(&daemon->local_zones->lock); + lock_rw_unlock(&ct->auth_zones->lock); + lock_rw_unlock(&env->auth_zones->lock); + lock_rw_unlock(&ct->auth_zones->rpz_lock); + lock_rw_unlock(&env->auth_zones->rpz_lock); + lock_rw_unlock(&ct->fwds->lock); + lock_rw_unlock(&env->fwds->lock); + lock_rw_unlock(&ct->hints->lock); + lock_rw_unlock(&env->hints->lock); + if(ct->anchors) { + lock_basic_unlock(&ct->anchors->lock); + lock_basic_unlock(&env->anchors->lock); + } + + return 1; +} + +/** fast reload, poll for ack incoming. */ +static void +fr_poll_for_ack(struct fast_reload_thread* fr) +{ + int loopexit = 0, bcount = 0; + uint32_t cmd; + ssize_t ret; + + if(fr->need_to_quit) + return; + /* Is there data? */ + if(!sock_poll_timeout(fr->commpair[1], -1, 1, 0, NULL)) { + log_err("fr_poll_for_ack: poll failed"); + return; + } + + /* Read the data */ + while(1) { + if(++loopexit > IPC_LOOP_MAX) { + log_err("fr_poll_for_ack: recv loops %s", + sock_strerror(errno)); + return; + } + ret = recv(fr->commpair[1], ((char*)&cmd)+bcount, + sizeof(cmd)-bcount, 0); + if(ret == -1) { + if( +#ifndef USE_WINSOCK + errno == EINTR || errno == EAGAIN +# ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +# endif +#else + WSAGetLastError() == WSAEINTR || + WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK +#endif + ) + continue; /* Try again. */ + log_err("fr_poll_for_ack: recv: %s", + sock_strerror(errno)); + return; + } else if(ret+(ssize_t)bcount != sizeof(cmd)) { + bcount += ret; + if((size_t)bcount < sizeof(cmd)) + continue; + } + break; + } + if(cmd == fast_reload_notification_exit) { + fr->need_to_quit = 1; + verbose(VERB_ALGO, "fast reload wait for ack: " + "exit notification received"); + return; + } + if(cmd != fast_reload_notification_reload_ack) { + verbose(VERB_ALGO, "fast reload wait for ack: " + "wrong notification %d", (int)cmd); + } +} + +/** fast reload thread, reload ipc communication to stop and start threads. */ +static int +fr_reload_ipc(struct fast_reload_thread* fr, struct config_file* newcfg, + struct fast_reload_construct* ct) +{ + int result = 1; + if(!fr->fr_nopause) { + fr_send_notification(fr, fast_reload_notification_reload_stop); + fr_poll_for_ack(fr); + } + if(!fr_reload_config(fr, newcfg, ct)) { + result = 0; + } + if(!fr->fr_nopause) { + fr_send_notification(fr, fast_reload_notification_reload_start); + fr_poll_for_ack(fr); + } + return result; +} + +/** fast reload thread, load config */ +static int +fr_load_config(struct fast_reload_thread* fr, struct timeval* time_read, + struct timeval* time_construct, struct timeval* time_reload) +{ + struct fast_reload_construct ct; + struct config_file* newcfg = NULL; + memset(&ct, 0, sizeof(ct)); + + /* Read file. */ + if(!fr_read_config(fr, &newcfg)) + return 0; + if(gettimeofday(time_read, NULL) < 0) + log_err("gettimeofday: %s", strerror(errno)); + if(fr_poll_for_quit(fr)) { + config_delete(newcfg); + return 1; + } + + /* Check if the config can be loaded */ + if(!fr_check_tag_defines(fr, newcfg)) { + config_delete(newcfg); + return 0; + } + if(!fr_check_compat_cfg(fr, newcfg)) { + config_delete(newcfg); + return 0; + } + if(!fr_check_nopause_cfg(fr, newcfg)) { + config_delete(newcfg); + return 0; + } + if(fr_poll_for_quit(fr)) { + config_delete(newcfg); + return 1; + } + + /* Construct items. */ + if(!fr_construct_from_config(fr, newcfg, &ct)) { + config_delete(newcfg); + if(!fr_output_printf(fr, "Could not construct from the " + "config, check for errors with unbound-checkconf, or " + "out of memory. The parse errors are printed in " + "the log.\n")) + return 0; + fr_send_notification(fr, fast_reload_notification_printout); + return 0; + } + if(gettimeofday(time_construct, NULL) < 0) + log_err("gettimeofday: %s", strerror(errno)); + if(fr_poll_for_quit(fr)) { + config_delete(newcfg); + fr_construct_clear(&ct); + return 1; + } + + /* Reload server. */ + if(!fr_reload_ipc(fr, newcfg, &ct)) { + config_delete(newcfg); + fr_construct_clear(&ct); + if(!fr_output_printf(fr, "error: reload failed\n")) + return 0; + fr_send_notification(fr, fast_reload_notification_printout); + return 0; + } + if(gettimeofday(time_reload, NULL) < 0) + log_err("gettimeofday: %s", strerror(errno)); + + if(fr_poll_for_quit(fr)) { + config_delete(newcfg); + fr_construct_clear(&ct); + return 1; + } + if(fr->fr_nopause) { + /* Poll every thread, with a no-work poll item over the + * command pipe. This makes the worker thread surely move + * to deal with that event, and thus the thread is no longer + * holding, eg. a string item from the old config struct. + * And then the old config struct can safely be deleted. + * Only needed when nopause is used, because without that + * the worker threads are already waiting on a command pipe + * item. This nopause command pipe item does not take work, + * it returns immediately, so it does not delay the workers. + * They can be polled one at a time. But its processing causes + * the worker to have released data items from old config. + * This also makes sure the threads are not holding locks on + * individual items in the local_zones, views, respip_set. */ + fr_send_notification(fr, + fast_reload_notification_reload_nopause_poll); + fr_poll_for_ack(fr); + } + + /* Delete old. */ + config_delete(newcfg); + fr_construct_clear(&ct); + return 1; +} + +/** fast reload thread. the thread main function */ +static void* fast_reload_thread_main(void* arg) +{ + struct fast_reload_thread* fast_reload_thread = (struct fast_reload_thread*)arg; + struct timeval time_start, time_read, time_construct, time_reload, + time_end; + log_thread_set(&fast_reload_thread->threadnum); + + verbose(VERB_ALGO, "start fast reload thread"); + if(fast_reload_thread->fr_verb >= 1) { + fr_init_time(&time_start, &time_read, &time_construct, + &time_reload, &time_end); + if(fr_poll_for_quit(fast_reload_thread)) + goto done; + } + + /* print output to the client */ + if(fast_reload_thread->fr_verb >= 1) { + if(!fr_output_printf(fast_reload_thread, "thread started\n")) + goto done_error; + fr_send_notification(fast_reload_thread, + fast_reload_notification_printout); + if(fr_poll_for_quit(fast_reload_thread)) + goto done; + } + + if(!fr_load_config(fast_reload_thread, &time_read, &time_construct, + &time_reload)) + goto done_error; + if(fr_poll_for_quit(fast_reload_thread)) + goto done; + + if(fast_reload_thread->fr_verb >= 1) { + if(!fr_finish_time(fast_reload_thread, &time_start, &time_read, + &time_construct, &time_reload, &time_end)) + goto done_error; + if(fr_poll_for_quit(fast_reload_thread)) + goto done; + } + + if(!fr_output_printf(fast_reload_thread, "ok\n")) + goto done_error; + fr_send_notification(fast_reload_thread, + fast_reload_notification_printout); + verbose(VERB_ALGO, "stop fast reload thread"); + /* If this is not an exit due to quit earlier, send regular done. */ + if(!fast_reload_thread->need_to_quit) + fr_send_notification(fast_reload_thread, + fast_reload_notification_done); + /* If during the fast_reload_notification_done send, + * fast_reload_notification_exit was received, ack it. If the + * thread is exiting due to quit received earlier, also ack it.*/ +done: + if(fast_reload_thread->need_to_quit) + fr_send_notification(fast_reload_thread, + fast_reload_notification_exited); + return NULL; +done_error: + verbose(VERB_ALGO, "stop fast reload thread with done_error"); + fr_send_notification(fast_reload_thread, + fast_reload_notification_done_error); + return NULL; +} +#endif /* !THREADS_DISABLED */ + +/** create a socketpair for bidirectional communication, false on failure */ +static int +create_socketpair(int* pair, struct ub_randstate* rand) +{ +#ifndef USE_WINSOCK + if(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) { + log_err("socketpair: %s", strerror(errno)); + return 0; + } + (void)rand; +#else + struct sockaddr_in addr, baddr, accaddr, connaddr; + socklen_t baddrlen, accaddrlen, connaddrlen; + uint8_t localhost[] = {127, 0, 0, 1}; + uint8_t nonce[16], recvnonce[16]; + size_t i; + int lst, pollin_event, bcount, loopcount; + int connect_poll_timeout = 200; /* msec to wait for connection */ + ssize_t ret; + pair[0] = -1; + pair[1] = -1; + for(i=0; i (socklen_t)sizeof(baddr)) { + log_err("create socketpair: getsockname returned addr too big"); + sock_close(lst); + sock_close(pair[1]); + pair[1] = -1; + return 0; + } + /* the socket is blocking */ + if(connect(pair[1], (struct sockaddr*)&baddr, baddrlen) == -1) { + log_err("create socketpair: connect: %s", + sock_strerror(errno)); + sock_close(lst); + sock_close(pair[1]); + pair[1] = -1; + return 0; + } + if(!sock_poll_timeout(lst, connect_poll_timeout, 1, 0, &pollin_event)) { + log_err("create socketpair: poll for accept failed: %s", + sock_strerror(errno)); + sock_close(lst); + sock_close(pair[1]); + pair[1] = -1; + return 0; + } + if(!pollin_event) { + log_err("create socketpair: poll timeout for accept"); + sock_close(lst); + sock_close(pair[1]); + pair[1] = -1; + return 0; + } + accaddrlen = (socklen_t)sizeof(accaddr); + pair[0] = accept(lst, (struct sockaddr*)&accaddr, &accaddrlen); + if(pair[0] == -1) { + log_err("create socketpair: accept: %s", sock_strerror(errno)); + sock_close(lst); + sock_close(pair[1]); + pair[1] = -1; + return 0; + } + if(accaddrlen > (socklen_t)sizeof(accaddr)) { + log_err("create socketpair: accept returned addr too big"); + sock_close(lst); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } + if(accaddr.sin_family != AF_INET || + memcmp(localhost, &accaddr.sin_addr, 4) != 0) { + log_err("create socketpair: accept from wrong address"); + sock_close(lst); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } + connaddrlen = (socklen_t)sizeof(connaddr); + if(getsockname(pair[1], (struct sockaddr*)&connaddr, &connaddrlen) + == -1) { + log_err("create socketpair: getsockname connectedaddr: %s", + sock_strerror(errno)); + sock_close(lst); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } + if(connaddrlen > (socklen_t)sizeof(connaddr)) { + log_err("create socketpair: getsockname connectedaddr returned addr too big"); + sock_close(lst); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } + if(connaddr.sin_family != AF_INET || + memcmp(localhost, &connaddr.sin_addr, 4) != 0) { + log_err("create socketpair: getsockname connectedaddr returned wrong address"); + sock_close(lst); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } + if(accaddr.sin_port != connaddr.sin_port) { + log_err("create socketpair: accept from wrong port"); + sock_close(lst); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } + sock_close(lst); + + loopcount = 0; + bcount = 0; + while(1) { + if(++loopcount > IPC_LOOP_MAX) { + log_err("create socketpair: send failed due to loop"); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } + ret = send(pair[1], (void*)(nonce+bcount), + sizeof(nonce)-bcount, 0); + if(ret == -1) { + if( +#ifndef USE_WINSOCK + errno == EINTR || errno == EAGAIN +# ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +# endif +#else + WSAGetLastError() == WSAEINTR || + WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK +#endif + ) + continue; /* Try again. */ + log_err("create socketpair: send: %s", sock_strerror(errno)); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } else if(ret+(ssize_t)bcount != sizeof(nonce)) { + bcount += ret; + if((size_t)bcount < sizeof(nonce)) + continue; + } + break; + } + + if(!sock_poll_timeout(pair[0], connect_poll_timeout, 1, 0, &pollin_event)) { + log_err("create socketpair: poll failed: %s", + sock_strerror(errno)); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } + if(!pollin_event) { + log_err("create socketpair: poll timeout for recv"); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } + + loopcount = 0; + bcount = 0; + while(1) { + if(++loopcount > IPC_LOOP_MAX) { + log_err("create socketpair: recv failed due to loop"); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } + ret = recv(pair[0], (void*)(recvnonce+bcount), + sizeof(nonce)-bcount, 0); + if(ret == -1) { + if( +#ifndef USE_WINSOCK + errno == EINTR || errno == EAGAIN +# ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +# endif +#else + WSAGetLastError() == WSAEINTR || + WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK +#endif + ) + continue; /* Try again. */ + log_err("create socketpair: recv: %s", sock_strerror(errno)); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } else if(ret == 0) { + log_err("create socketpair: stream closed"); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } else if(ret+(ssize_t)bcount != sizeof(nonce)) { + bcount += ret; + if((size_t)bcount < sizeof(nonce)) + continue; + } + break; + } + + if(memcmp(nonce, recvnonce, sizeof(nonce)) != 0) { + log_err("create socketpair: recv wrong nonce"); + sock_close(pair[0]); + sock_close(pair[1]); + pair[0] = -1; + pair[1] = -1; + return 0; + } +#endif + return 1; +} + +/** fast reload thread. setup the thread info */ +static int +fast_reload_thread_setup(struct worker* worker, int fr_verb, int fr_nopause, + int fr_drop_mesh) +{ + struct fast_reload_thread* fr; + int numworkers = worker->daemon->num; + worker->daemon->fast_reload_thread = (struct fast_reload_thread*) + calloc(1, sizeof(*worker->daemon->fast_reload_thread)); + if(!worker->daemon->fast_reload_thread) + return 0; + fr = worker->daemon->fast_reload_thread; + fr->fr_verb = fr_verb; + fr->fr_nopause = fr_nopause; + fr->fr_drop_mesh = fr_drop_mesh; + worker->daemon->fast_reload_drop_mesh = fr->fr_drop_mesh; + /* The thread id printed in logs, numworker+1 is the dnstap thread. + * This is numworkers+2. */ + fr->threadnum = numworkers+2; + fr->commpair[0] = -1; + fr->commpair[1] = -1; + fr->commreload[0] = -1; + fr->commreload[1] = -1; + if(!create_socketpair(fr->commpair, worker->daemon->rand)) { + free(fr); + worker->daemon->fast_reload_thread = NULL; + return 0; + } + fr->worker = worker; + fr->fr_output = (struct config_strlist_head*)calloc(1, + sizeof(*fr->fr_output)); + if(!fr->fr_output) { + sock_close(fr->commpair[0]); + sock_close(fr->commpair[1]); + free(fr); + worker->daemon->fast_reload_thread = NULL; + return 0; + } + if(!create_socketpair(fr->commreload, worker->daemon->rand)) { + sock_close(fr->commpair[0]); + sock_close(fr->commpair[1]); + free(fr->fr_output); + free(fr); + worker->daemon->fast_reload_thread = NULL; + return 0; + } + lock_basic_init(&fr->fr_output_lock); + lock_protect(&fr->fr_output_lock, fr->fr_output, + sizeof(*fr->fr_output)); + return 1; +} + +/** fast reload, delete auth zone change list */ +static void +fr_auth_change_list_delete( + struct fast_reload_auth_change* auth_zone_change_list) +{ + struct fast_reload_auth_change* item, *next; + item = auth_zone_change_list; + while(item) { + next = item->next; + free(item); + item = next; + } +} + +/** fast reload thread. desetup and delete the thread info. */ +static void +fast_reload_thread_desetup(struct fast_reload_thread* fast_reload_thread) +{ + if(!fast_reload_thread) + return; + if(fast_reload_thread->service_event && + fast_reload_thread->service_event_is_added) { + ub_event_del(fast_reload_thread->service_event); + fast_reload_thread->service_event_is_added = 0; + } + if(fast_reload_thread->service_event) + ub_event_free(fast_reload_thread->service_event); + sock_close(fast_reload_thread->commpair[0]); + sock_close(fast_reload_thread->commpair[1]); + sock_close(fast_reload_thread->commreload[0]); + sock_close(fast_reload_thread->commreload[1]); + if(fast_reload_thread->printq) { + fr_main_perform_printout(fast_reload_thread); + /* If it is empty now, there is nothing to print on fd. */ + if(fr_printq_empty(fast_reload_thread->printq)) { + fr_printq_delete(fast_reload_thread->printq); + } else { + /* Keep the printq around to printout the remaining + * text to the remote client. Until it is done, it + * sits on a list, that is in the daemon struct. + * The event can then spool the remaining text to the + * remote client and eventually delete itself from the + * callback. */ + fr_printq_list_insert(fast_reload_thread->printq, + fast_reload_thread->worker->daemon); + fast_reload_thread->printq = NULL; + } + } + lock_basic_destroy(&fast_reload_thread->fr_output_lock); + if(fast_reload_thread->fr_output) { + config_delstrlist(fast_reload_thread->fr_output->first); + free(fast_reload_thread->fr_output); + } + fr_auth_change_list_delete(fast_reload_thread->auth_zone_change_list); + + free(fast_reload_thread); +} + +/** + * Fast reload thread, send a command to the thread. Blocking on timeout. + * It handles received input from the thread, if any is received. + */ +static void +fr_send_cmd_to(struct fast_reload_thread* fr, + enum fast_reload_notification status, int check_cmds, int blocking) +{ + int outevent, loopexit = 0, bcount = 0; + uint32_t cmd; + ssize_t ret; + verbose(VERB_ALGO, "send notification to fast reload thread: %s", + fr_notification_to_string(status)); + cmd = status; + while(1) { + if(++loopexit > IPC_LOOP_MAX) { + log_err("send notification to fast reload: could not send notification: loop"); + return; + } + if(check_cmds) + fr_check_cmd_from_thread(fr); + /* wait for socket to become writable */ + if(!sock_poll_timeout(fr->commpair[0], + (blocking?-1:IPC_NOTIFICATION_WAIT), + 0, 1, &outevent)) { + log_err("send notification to fast reload: poll failed"); + return; + } + if(!outevent) + continue; + /* keep static analyzer happy; send(-1,..) */ + log_assert(fr->commpair[0] >= 0); + ret = send(fr->commpair[0], ((char*)&cmd)+bcount, + sizeof(cmd)-bcount, 0); + if(ret == -1) { + if( +#ifndef USE_WINSOCK + errno == EINTR || errno == EAGAIN +# ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +# endif +#else + WSAGetLastError() == WSAEINTR || + WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK +#endif + ) + continue; /* Try again. */ + log_err("send notification to fast reload: send: %s", + sock_strerror(errno)); + return; + } else if(ret+(ssize_t)bcount != sizeof(cmd)) { + bcount += ret; + if((size_t)bcount < sizeof(cmd)) + continue; + } + break; + } +} + +/** Fast reload, the main thread handles that the fast reload thread has + * exited. */ +static void +fr_main_perform_done(struct fast_reload_thread* fr) +{ + struct worker* worker = fr->worker; + verbose(VERB_ALGO, "join with fastreload thread"); + ub_thread_join(fr->tid); + verbose(VERB_ALGO, "joined with fastreload thread"); + fast_reload_thread_desetup(fr); + worker->daemon->fast_reload_thread = NULL; +} + +/** Append strlist after strlist */ +static void +cfg_strlist_append_listhead(struct config_strlist_head* list, + struct config_strlist_head* more) +{ + if(!more->first) + return; + if(list->last) + list->last->next = more->first; + else + list->first = more->first; + list->last = more->last; +} + +/** Fast reload, the remote control thread handles that the fast reload thread + * has output to be printed, on the linked list that is locked. */ +static void +fr_main_perform_printout(struct fast_reload_thread* fr) +{ + struct config_strlist_head out; + + /* Fetch the list of items to be printed */ + lock_basic_lock(&fr->fr_output_lock); + out.first = fr->fr_output->first; + out.last = fr->fr_output->last; + fr->fr_output->first = NULL; + fr->fr_output->last = NULL; + lock_basic_unlock(&fr->fr_output_lock); + + if(!fr->printq || !fr->printq->client_cp) { + /* There is no output socket, delete it. */ + config_delstrlist(out.first); + return; + } + + /* Put them on the output list, not locked because the list + * producer and consumer are both owned by the remote control thread, + * it moves the items to the list for printing in the event callback + * for the client_cp. */ + cfg_strlist_append_listhead(fr->printq->to_print, &out); + + /* Set the client_cp to output if not already */ + if(!fr->printq->client_cp->event_added) + comm_point_listen_for_rw(fr->printq->client_cp, 0, 1); +} + +/** fast reload, receive ack from workers that they are waiting, run + * by the mainthr after sending them reload_stop. */ +static void +fr_read_ack_from_workers(struct fast_reload_thread* fr) +{ + struct daemon* daemon = fr->worker->daemon; + /* Every worker sends one byte, wait for num-1 bytes. */ + int count=0, total=daemon->num-1; + while(count < total) { + uint8_t r; + ssize_t ret; + ret = recv(fr->commreload[0], (void*)&r, 1, 0); + if(ret == -1) { + if( +#ifndef USE_WINSOCK + errno == EINTR || errno == EAGAIN +# ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +# endif +#else + WSAGetLastError() == WSAEINTR || + WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK +#endif + ) + continue; /* Try again */ + log_err("worker reload ack: recv failed: %s", + sock_strerror(errno)); + return; + } + count++; + verbose(VERB_ALGO, "worker reload ack from (uint8_t)%d", + (int)r); + } +} + +/** fast reload, poll for reload_start in mainthr waiting on a notification + * from the fast reload thread. */ +static void +fr_poll_for_reload_start(struct fast_reload_thread* fr) +{ + int loopexit = 0, bcount = 0; + uint32_t cmd; + ssize_t ret; + + /* Is there data? */ + if(!sock_poll_timeout(fr->commpair[0], -1, 1, 0, NULL)) { + log_err("fr_poll_for_reload_start: poll failed"); + return; + } + + /* Read the data */ + while(1) { + if(++loopexit > IPC_LOOP_MAX) { + log_err("fr_poll_for_reload_start: recv loops %s", + sock_strerror(errno)); + return; + } + ret = recv(fr->commpair[0], ((char*)&cmd)+bcount, + sizeof(cmd)-bcount, 0); + if(ret == -1) { + if( +#ifndef USE_WINSOCK + errno == EINTR || errno == EAGAIN +# ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +# endif +#else + WSAGetLastError() == WSAEINTR || + WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK +#endif + ) + continue; /* Try again. */ + log_err("fr_poll_for_reload_start: recv: %s", + sock_strerror(errno)); + return; + } else if(ret+(ssize_t)bcount != sizeof(cmd)) { + bcount += ret; + if((size_t)bcount < sizeof(cmd)) + continue; + } + break; + } + if(cmd != fast_reload_notification_reload_start) { + verbose(VERB_ALGO, "fast reload wait for ack: " + "wrong notification %d", (int)cmd); + } +} + +/** Pick up the worker mesh changes, after fast reload. */ +static void +fr_worker_pickup_mesh(struct worker* worker) +{ + struct mesh_area* mesh = worker->env.mesh; + struct config_file* cfg = worker->env.cfg; + mesh->use_response_ip = worker->daemon->use_response_ip; + mesh->use_rpz = worker->daemon->use_rpz; + mesh->max_reply_states = cfg->num_queries_per_thread; + mesh->max_forever_states = (mesh->max_reply_states+1)/2; +#ifndef S_SPLINT_S + mesh->jostle_max.tv_sec = (time_t)(cfg->jostle_time / 1000); + mesh->jostle_max.tv_usec = (time_t)((cfg->jostle_time % 1000)*1000); +#endif +} + +/** + * Remove the old tcl_addr entries from the open connections. + * They are only incremented when an accept is performed on a tcp comm point. + * @param front: listening comm ports of the worker. + */ +static void +tcl_remove_old(struct listen_dnsport* front) +{ + struct listen_list* l; + l = front->cps; + while(l) { + if(l->com->type == comm_tcp_accept) { + int i; + for(i=0; icom->max_tcp_count; i++) { + if(l->com->tcp_handlers[i]->tcl_addr) { + /* Because the increment of the + * connection limit was in the old + * tcl list, the new list does not + * need a decrement. With NULL it is + * not decremented when the connection + * is done, and also there is no + * reference to the old connection + * limit structure. */ + l->com->tcp_handlers[i]->tcl_addr = + NULL; + } + } + } + l = l->next; + } +} + +/** Stop zonemd lookup */ +static void +auth_zone_zonemd_stop_lookup(struct auth_zone* z, struct mesh_area* mesh) +{ + struct query_info qinfo; + uint16_t qflags = BIT_RD; + qinfo.qname_len = z->namelen; + qinfo.qname = z->name; + qinfo.qclass = z->dclass; + qinfo.qtype = z->zonemd_callback_qtype; + qinfo.local_alias = NULL; + + mesh_remove_callback(mesh, &qinfo, qflags, + &auth_zonemd_dnskey_lookup_callback, z); +} + +/** Pick up the auth zone locks. */ +static void +fr_pickup_auth_locks(struct worker* worker, struct auth_zone* namez, + struct auth_zone* old_z, struct auth_zone* new_z, + struct auth_xfer** xfr, struct auth_xfer** loadxfr) +{ + uint8_t nm[LDNS_MAX_DOMAINLEN+1]; + size_t nmlen; + uint16_t dclass; + + log_assert(namez->namelen <= sizeof(nm)); + lock_rw_rdlock(&namez->lock); + nmlen = namez->namelen; + dclass = namez->dclass; + memmove(nm, namez->name, nmlen); + lock_rw_unlock(&namez->lock); + + lock_rw_wrlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock); + lock_rw_wrlock(&worker->env.auth_zones->lock); + if(new_z) { + lock_rw_wrlock(&new_z->lock); + } + if(old_z) { + lock_rw_wrlock(&old_z->lock); + } + if(loadxfr) + *loadxfr = auth_xfer_find(worker->daemon->fast_reload_thread-> + old_auth_zones, nm, nmlen, dclass); + if(xfr) + *xfr = auth_xfer_find(worker->env.auth_zones, nm, nmlen, + dclass); + if(loadxfr && *loadxfr) { + lock_basic_lock(&(*loadxfr)->lock); + } + if(xfr && *xfr) { + lock_basic_lock(&(*xfr)->lock); + } +} + +/** Fast reload, worker picks up deleted auth zone */ +static void +fr_worker_auth_del(struct worker* worker, struct fast_reload_auth_change* item, + int for_change) +{ + int released = 0; /* Did this routine release callbacks. */ + struct auth_xfer* xfr = NULL; + + lock_rw_wrlock(&item->old_z->lock); + if(item->old_z->zonemd_callback_env && + item->old_z->zonemd_callback_env->worker == worker){ + /* This worker was performing a zonemd lookup, + * stop the lookup and remove that entry. */ + auth_zone_zonemd_stop_lookup(item->old_z, worker->env.mesh); + item->old_z->zonemd_callback_env = NULL; + } + lock_rw_unlock(&item->old_z->lock); + + fr_pickup_auth_locks(worker, item->old_z, item->old_z, NULL, &xfr, + NULL); + lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock); + lock_rw_unlock(&worker->env.auth_zones->lock); + lock_rw_unlock(&item->old_z->lock); + if(xfr) { + /* Release callbacks on the xfr, if this worker holds them. */ + if(xfr->task_nextprobe->worker == worker || + xfr->task_probe->worker == worker || + xfr->task_transfer->worker == worker) { + released = 1; + xfr_disown_tasks(xfr, worker); + } + lock_basic_unlock(&xfr->lock); + } + + if(!for_change && (released || worker->thread_num == 0)) { + /* See if the xfr item can be deleted. */ + xfr = NULL; + fr_pickup_auth_locks(worker, item->old_z, item->old_z, NULL, + &xfr, NULL); + lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock); + lock_rw_unlock(&item->old_z->lock); + if(xfr && xfr->task_nextprobe->worker == NULL && + xfr->task_probe->worker == NULL && + xfr->task_transfer->worker == NULL) { + (void)rbtree_delete(&worker->env.auth_zones->xtree, + &xfr->node); + lock_rw_unlock(&worker->env.auth_zones->lock); + lock_basic_unlock(&xfr->lock); + auth_xfer_delete(xfr); + } else { + lock_rw_unlock(&worker->env.auth_zones->lock); + if(xfr) { + lock_basic_unlock(&xfr->lock); + } + } + } +} + +/** Fast reload, auth xfer config is picked up */ +static void +auth_xfr_pickup_config(struct auth_xfer* loadxfr, struct auth_xfer* xfr) +{ + struct auth_master *probe_masters, *transfer_masters; + log_assert(loadxfr->namelen == xfr->namelen); + log_assert(loadxfr->namelabs == xfr->namelabs); + log_assert(loadxfr->dclass == xfr->dclass); + + /* The lists can be swapped in, the other xfr struct will be deleted + * afterwards. */ + probe_masters = xfr->task_probe->masters; + transfer_masters = xfr->task_transfer->masters; + xfr->task_probe->masters = loadxfr->task_probe->masters; + xfr->task_transfer->masters = loadxfr->task_transfer->masters; + loadxfr->task_probe->masters = probe_masters; + loadxfr->task_transfer->masters = transfer_masters; +} + +/** Fast reload, worker picks up added auth zone */ +static void +fr_worker_auth_add(struct worker* worker, struct fast_reload_auth_change* item, + int for_change) +{ + struct auth_xfer* xfr = NULL, *loadxfr = NULL; + + /* Start zone transfers and lookups. */ + fr_pickup_auth_locks(worker, item->new_z, NULL, item->new_z, &xfr, + &loadxfr); + if(xfr == NULL && item->new_z->zone_is_slave) { + /* The xfr item needs to be created. The auth zones lock + * is held to make this possible. */ + xfr = auth_xfer_create(worker->env.auth_zones, item->new_z); + auth_xfr_pickup_config(loadxfr, xfr); + /* Serial information is copied into the xfr struct. */ + if(!xfr_find_soa(item->new_z, xfr)) { + xfr->serial = 0; + } + } else if(for_change && xfr) { + if(!xfr_find_soa(item->new_z, xfr)) { + xfr->serial = 0; + } + } + lock_rw_unlock(&item->new_z->lock); + lock_rw_unlock(&worker->env.auth_zones->lock); + lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock); + if(loadxfr) { + lock_basic_unlock(&loadxfr->lock); + } + if(xfr) { + auth_xfer_pickup_initial_zone(xfr, &worker->env); + if(for_change) { + xfr->task_probe->only_lookup = 0; + } + lock_basic_unlock(&xfr->lock); + } + + /* Perform ZONEMD verification lookups. */ + lock_rw_wrlock(&item->new_z->lock); + /* holding only the new_z lock */ + auth_zone_verify_zonemd(item->new_z, &worker->env, + &worker->env.mesh->mods, NULL, 0, 1); + lock_rw_unlock(&item->new_z->lock); +} + +/** Fast reload, worker picks up changed auth zone */ +static void +fr_worker_auth_cha(struct worker* worker, struct fast_reload_auth_change* item) +{ + int todelete = 0; + struct auth_xfer* loadxfr = NULL, *xfr = NULL; + /* Since the zone has been changed, by rereading it from zone file, + * existing transfers and probes are likely for the old version. + * Stop them, and start new ones if needed. */ + fr_worker_auth_del(worker, item, 1); + + if(worker->thread_num != 0) + return; + + /* The old callbacks are stopped, tasks have been disowned. The + * new config contents can be picked up. SOA information is picked + * up in the auth_add routine, as it has the new_z ready. */ + + fr_pickup_auth_locks(worker, item->new_z, item->old_z, item->new_z, + &xfr, &loadxfr); + + /* The xfr is not there any more if the zone is not set to have + * zone transfers. Or the xfr needs to be created if it is set to + * have zone transfers. */ + if(loadxfr && xfr) { + /* Copy the config from loadxfr to the xfr in current use. */ + auth_xfr_pickup_config(loadxfr, xfr); + } else if(!loadxfr && xfr) { + /* Delete the xfr. */ + (void)rbtree_delete(&worker->env.auth_zones->xtree, + &xfr->node); + todelete = 1; + item->new_z->zone_is_slave = 0; + } else if(loadxfr && !xfr) { + /* Create the xfr. */ + xfr = auth_xfer_create(worker->env.auth_zones, item->new_z); + auth_xfr_pickup_config(loadxfr, xfr); + item->new_z->zone_is_slave = 1; + } + lock_rw_unlock(&item->new_z->lock); + lock_rw_unlock(&item->old_z->lock); + lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock); + lock_rw_unlock(&worker->env.auth_zones->lock); + if(loadxfr) { + lock_basic_unlock(&loadxfr->lock); + } + if(xfr) { + lock_basic_unlock(&xfr->lock); + } + if(todelete) { + auth_xfer_delete(xfr); + } + + fr_worker_auth_add(worker, item, 1); +} + +/** Fast reload, the worker picks up changes in auth zones. */ +static void +fr_worker_pickup_auth_changes(struct worker* worker, + struct fast_reload_auth_change* auth_zone_change_list) +{ + struct fast_reload_auth_change* item; + for(item = auth_zone_change_list; item; item = item->next) { + if(item->is_deleted) { + fr_worker_auth_del(worker, item, 0); + } + if(item->is_added) { + if(worker->thread_num == 0) { + fr_worker_auth_add(worker, item, 0); + } + } + if(item->is_changed) { + fr_worker_auth_cha(worker, item); + } + } +} + +/** Fast reload, the worker picks up changes in outside_network. */ +static void +fr_worker_pickup_outside_network(struct worker* worker) +{ + struct outside_network* outnet = worker->back; + struct config_file* cfg = worker->env.cfg; + outnet->use_caps_for_id = cfg->use_caps_bits_for_id; + outnet->unwanted_threshold = cfg->unwanted_threshold; + outnet->tls_use_sni = cfg->tls_use_sni; + outnet->tcp_mss = cfg->outgoing_tcp_mss; + outnet->ip_dscp = cfg->ip_dscp; + outnet->max_reuse_tcp_queries = cfg->max_reuse_tcp_queries; + outnet->tcp_reuse_timeout = cfg->tcp_reuse_timeout; + outnet->tcp_auth_query_timeout = cfg->tcp_auth_query_timeout; + outnet->delayclose = cfg->delay_close; + if(outnet->delayclose) { +#ifndef S_SPLINT_S + outnet->delay_tv.tv_sec = cfg->delay_close/1000; + outnet->delay_tv.tv_usec = (cfg->delay_close%1000)*1000; +#endif + } +} + +void +fast_reload_worker_pickup_changes(struct worker* worker) +{ + /* The pickup of changes is called when the fast reload has + * a syncronized moment, and all the threads are paused and the + * reload has been applied. Then the worker can pick up the new + * changes and store them in worker-specific structs. + * The pickup is also called when there is no pause, and then + * it is called after the reload has completed, and the worker + * get a signal to release old information, it can then pick + * up the new information. But in the mean time, the reload has + * swapped in trees, and the worker has been running with the + * older information for some time. */ + fr_worker_pickup_mesh(worker); + + /* If the tcp connection limit has changed, the open connections + * need to remove their reference for the old tcp limits counters. */ + if(worker->daemon->fast_reload_tcl_has_changes) + tcl_remove_old(worker->front); + + /* If there are zonemd lookups, but the zone was deleted, the + * lookups should be cancelled. */ + fr_worker_pickup_auth_changes(worker, + worker->daemon->fast_reload_thread->auth_zone_change_list); +#ifdef USE_CACHEDB + worker->env.cachedb_enabled = worker->daemon->env->cachedb_enabled; +#endif + fr_worker_pickup_outside_network(worker); +} + +/** fast reload thread, handle reload_stop notification, send reload stop + * to other threads over IPC and collect their ack. When that is done, + * ack to the caller, the fast reload thread, and wait for it to send start. */ +static void +fr_main_perform_reload_stop(struct fast_reload_thread* fr) +{ + struct daemon* daemon = fr->worker->daemon; + int i; + + /* Send reload_stop to other threads. */ + for(i=0; inum; i++) { + if(i == fr->worker->thread_num) + continue; /* Do not send to ourselves. */ + worker_send_cmd(daemon->workers[i], worker_cmd_reload_stop); + } + + /* Wait for the other threads to ack. */ + fr_read_ack_from_workers(fr); + + /* Send ack to fast reload thread. */ + fr_send_cmd_to(fr, fast_reload_notification_reload_ack, 0, 1); + + /* Wait for reload_start from fast reload thread to resume. */ + fr_poll_for_reload_start(fr); + + /* Send reload_start to other threads */ + for(i=0; inum; i++) { + if(i == fr->worker->thread_num) + continue; /* Do not send to ourselves. */ + worker_send_cmd(daemon->workers[i], worker_cmd_reload_start); + } + + /* Pick up changes for this worker. */ + if(fr->worker->daemon->fast_reload_drop_mesh) { + verbose(VERB_ALGO, "worker: drop mesh queries after reload"); + mesh_delete_all(fr->worker->env.mesh); + } + fast_reload_worker_pickup_changes(fr->worker); + + /* Wait for the other threads to ack. */ + fr_read_ack_from_workers(fr); + + /* Send ack to fast reload thread. */ + fr_send_cmd_to(fr, fast_reload_notification_reload_ack, 0, 1); + + verbose(VERB_ALGO, "worker resume after reload"); +} + +/** Fast reload, the main thread performs the nopause poll. It polls every + * other worker thread briefly over the command pipe ipc. The command takes + * no time for the worker, it can return immediately. After that it sends + * an acknowledgement to the fastreload thread. */ +static void +fr_main_perform_reload_nopause_poll(struct fast_reload_thread* fr) +{ + struct daemon* daemon = fr->worker->daemon; + int i; + + /* Send the reload_poll to other threads. They can respond + * one at a time. */ + for(i=0; inum; i++) { + if(i == fr->worker->thread_num) + continue; /* Do not send to ourselves. */ + worker_send_cmd(daemon->workers[i], worker_cmd_reload_poll); + } + + /* Wait for the other threads to ack. */ + fr_read_ack_from_workers(fr); + fast_reload_worker_pickup_changes(fr->worker); + + /* Send ack to fast reload thread. */ + fr_send_cmd_to(fr, fast_reload_notification_reload_ack, 0, 1); +} + +/** Fast reload, perform the command received from the fast reload thread */ +static void +fr_main_perform_cmd(struct fast_reload_thread* fr, + enum fast_reload_notification status) +{ + verbose(VERB_ALGO, "main perform fast reload status: %s", + fr_notification_to_string(status)); + if(status == fast_reload_notification_printout) { + fr_main_perform_printout(fr); + } else if(status == fast_reload_notification_done || + status == fast_reload_notification_done_error || + status == fast_reload_notification_exited) { + fr_main_perform_done(fr); + } else if(status == fast_reload_notification_reload_stop) { + fr_main_perform_reload_stop(fr); + } else if(status == fast_reload_notification_reload_nopause_poll) { + fr_main_perform_reload_nopause_poll(fr); + } else { + log_err("main received unknown status from fast reload: %d %s", + (int)status, fr_notification_to_string(status)); + } +} + +/** Fast reload, handle command from fast reload to the main thread. */ +static void +fr_main_handle_cmd(struct fast_reload_thread* fr) +{ + enum fast_reload_notification status; + ssize_t ret; + /* keep static analyzer happy; recv(-1,..) */ + log_assert(fr->commpair[0] >= 0); + ret = recv(fr->commpair[0], + ((char*)&fr->service_read_cmd)+fr->service_read_cmd_count, + sizeof(fr->service_read_cmd)-fr->service_read_cmd_count, 0); + if(ret == -1) { + if( +#ifndef USE_WINSOCK + errno == EINTR || errno == EAGAIN +# ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +# endif +#else + WSAGetLastError() == WSAEINTR || + WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK +#endif + ) + return; /* Continue later. */ + log_err("read cmd from fast reload thread, recv: %s", + sock_strerror(errno)); + return; + } else if(ret == 0) { + verbose(VERB_ALGO, "closed connection from fast reload thread"); + fr->service_read_cmd_count = 0; + /* handle this like an error */ + fr->service_read_cmd = fast_reload_notification_done_error; + } else if(ret + (ssize_t)fr->service_read_cmd_count < + (ssize_t)sizeof(fr->service_read_cmd)) { + fr->service_read_cmd_count += ret; + /* Continue later. */ + return; + } + status = fr->service_read_cmd; + fr->service_read_cmd = 0; + fr->service_read_cmd_count = 0; + fr_main_perform_cmd(fr, status); +} + +/** Fast reload, poll for and handle cmd from fast reload thread. */ +static void +fr_check_cmd_from_thread(struct fast_reload_thread* fr) +{ + int inevent = 0; + struct worker* worker = fr->worker; + /* Stop in case the thread has exited, or there is no read event. */ + while(worker->daemon->fast_reload_thread) { + if(!sock_poll_timeout(fr->commpair[0], 0, 1, 0, &inevent)) { + log_err("check for cmd from fast reload thread: " + "poll failed"); + return; + } + if(!inevent) + return; + fr_main_handle_cmd(fr); + } +} + +void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(bits), + void* arg) +{ + struct fast_reload_thread* fast_reload_thread = + (struct fast_reload_thread*)arg; + struct worker* worker = fast_reload_thread->worker; + + /* Read and handle the command */ + fr_main_handle_cmd(fast_reload_thread); + if(worker->daemon->fast_reload_thread != NULL) { + /* If not exited, see if there are more pending statuses + * from the fast reload thread. */ + fr_check_cmd_from_thread(fast_reload_thread); + } +} + +#ifdef HAVE_SSL +/** fast reload, send client item over SSL. Returns number of bytes + * printed, 0 on wait later, or -1 on failure. */ +static int +fr_client_send_item_ssl(struct fast_reload_printq* printq) +{ + int r; + ERR_clear_error(); + r = SSL_write(printq->remote.ssl, + printq->client_item+printq->client_byte_count, + printq->client_len - printq->client_byte_count); + if(r <= 0) { + int want = SSL_get_error(printq->remote.ssl, r); + if(want == SSL_ERROR_ZERO_RETURN) { + log_err("fast_reload print to remote client: " + "SSL_write says connection closed."); + return -1; + } else if(want == SSL_ERROR_WANT_READ) { + /* wait for read condition */ + printq->client_cp->ssl_shake_state = comm_ssl_shake_hs_read; + comm_point_listen_for_rw(printq->client_cp, 1, 0); + return 0; + } else if(want == SSL_ERROR_WANT_WRITE) { +#ifdef USE_WINSOCK + ub_winsock_tcp_wouldblock(comm_point_internal(printq->client_cp), UB_EV_WRITE); +#endif + return 0; /* write more later */ + } else if(want == SSL_ERROR_SYSCALL) { +#ifdef EPIPE + if(errno == EPIPE && verbosity < 2) { + /* silence 'broken pipe' */ + return -1; + } +#endif + if(errno != 0) + log_err("fast_reload print to remote client: " + "SSL_write syscall: %s", + sock_strerror(errno)); + return -1; + } + log_crypto_err_io("fast_reload print to remote client: " + "could not SSL_write", want); + return -1; + } + return r; +} +#endif /* HAVE_SSL */ + +/** fast reload, send client item for fd, returns bytes sent, or 0 for wait + * later, or -1 on failure. */ +static int +fr_client_send_item_fd(struct fast_reload_printq* printq) +{ + int r; + r = (int)send(printq->remote.fd, + printq->client_item+printq->client_byte_count, + printq->client_len - printq->client_byte_count, 0); + if(r == -1) { + if( +#ifndef USE_WINSOCK + errno == EINTR || errno == EAGAIN +# ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +# endif +#else + WSAGetLastError() == WSAEINTR || + WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK +#endif + ) { +#ifdef USE_WINSOCK + ub_winsock_tcp_wouldblock(comm_point_internal(printq->client_cp), UB_EV_WRITE); +#endif + return 0; /* Try again. */ + } + log_err("fast_reload print to remote client: send failed: %s", + sock_strerror(errno)); + return -1; + } + return r; +} + +/** fast reload, send current client item. false on failure or wait later. */ +static int +fr_client_send_item(struct fast_reload_printq* printq) +{ + int r; +#ifdef HAVE_SSL + if(printq->remote.ssl) { + r = fr_client_send_item_ssl(printq); + } else { +#endif + r = fr_client_send_item_fd(printq); +#ifdef HAVE_SSL + } +#endif + if(r == 0) { + /* Wait for later. */ + return 0; + } else if(r == -1) { + /* It failed, close comm point and stop sending. */ + fr_printq_remove(printq); + return 0; + } + printq->client_byte_count += r; + if(printq->client_byte_count < printq->client_len) + return 0; /* Print more later. */ + return 1; +} + +/** fast reload, pick up the next item to print */ +static void +fr_client_pickup_next_item(struct fast_reload_printq* printq) +{ + struct config_strlist* item; + /* Pop first off the list. */ + if(!printq->to_print->first) { + printq->client_item = NULL; + printq->client_len = 0; + printq->client_byte_count = 0; + return; + } + item = printq->to_print->first; + if(item->next) { + printq->to_print->first = item->next; + } else { + printq->to_print->first = NULL; + printq->to_print->last = NULL; + } + item->next = NULL; + printq->client_len = 0; + printq->client_byte_count = 0; + printq->client_item = item->str; + item->str = NULL; + free(item); + /* The len is the number of bytes to print out, and thus excludes + * the terminator zero. */ + if(printq->client_item) + printq->client_len = (int)strlen(printq->client_item); +} + +int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c), void* arg, + int err, struct comm_reply* ATTR_UNUSED(rep)) +{ + struct fast_reload_printq* printq = (struct fast_reload_printq*)arg; + if(!printq->client_cp) { + fr_printq_remove(printq); + return 0; /* the output is closed and deleted */ + } + if(err != NETEVENT_NOERROR) { + verbose(VERB_ALGO, "fast reload client: error, close it"); + fr_printq_remove(printq); + return 0; + } +#ifdef HAVE_SSL + if(printq->client_cp->ssl_shake_state == comm_ssl_shake_hs_read) { + /* read condition satisfied back to writing */ + comm_point_listen_for_rw(printq->client_cp, 0, 1); + printq->client_cp->ssl_shake_state = comm_ssl_shake_none; + } +#endif /* HAVE_SSL */ + + /* Pickup an item if there are none */ + if(!printq->client_item) { + fr_client_pickup_next_item(printq); + } + if(!printq->client_item) { + if(printq->in_list) { + /* Nothing more to print, it can be removed. */ + fr_printq_remove(printq); + return 0; + } + /* Done with printing for now. */ + comm_point_stop_listening(printq->client_cp); + return 0; + } + + /* Try to print out a number of items, if they can print in full. */ + while(printq->client_item) { + /* Send current item, if any. */ + if(printq->client_item && printq->client_len != 0 && + printq->client_byte_count < printq->client_len) { + if(!fr_client_send_item(printq)) + return 0; + } + + /* The current item is done. */ + if(printq->client_item) { + free(printq->client_item); + printq->client_item = NULL; + printq->client_len = 0; + printq->client_byte_count = 0; + } + if(!printq->to_print->first) { + if(printq->in_list) { + /* Nothing more to print, it can be removed. */ + fr_printq_remove(printq); + return 0; + } + /* Done with printing for now. */ + comm_point_stop_listening(printq->client_cp); + return 0; + } + fr_client_pickup_next_item(printq); + } + + return 0; +} + +#ifndef THREADS_DISABLED +/** fast reload printq create */ +static struct fast_reload_printq* +fr_printq_create(struct comm_point* c, struct worker* worker) +{ + struct fast_reload_printq* printq = calloc(1, sizeof(*printq)); + if(!printq) + return NULL; + printq->to_print = calloc(1, sizeof(*printq->to_print)); + if(!printq->to_print) { + free(printq); + return NULL; + } + printq->worker = worker; + printq->client_cp = c; + printq->client_cp->callback = fast_reload_client_callback; + printq->client_cp->cb_arg = printq; + return printq; +} +#endif /* !THREADS_DISABLED */ + +/** fast reload printq delete */ +static void +fr_printq_delete(struct fast_reload_printq* printq) +{ + if(!printq) + return; +#ifdef HAVE_SSL + if(printq->remote.ssl) { + SSL_shutdown(printq->remote.ssl); + SSL_free(printq->remote.ssl); + } +#endif + comm_point_delete(printq->client_cp); + if(printq->to_print) { + config_delstrlist(printq->to_print->first); + free(printq->to_print); + } + free(printq); +} + +/** fast reload printq, returns true if the list is empty and no item */ +static int +fr_printq_empty(struct fast_reload_printq* printq) +{ + if(printq->to_print->first == NULL && printq->client_item == NULL) + return 1; + return 0; +} + +/** fast reload printq, insert onto list */ +static void +fr_printq_list_insert(struct fast_reload_printq* printq, struct daemon* daemon) +{ + if(printq->in_list) + return; + printq->next = daemon->fast_reload_printq_list; + if(printq->next) + printq->next->prev = printq; + printq->prev = NULL; + printq->in_list = 1; + daemon->fast_reload_printq_list = printq; +} + +/** fast reload printq delete list */ +void +fast_reload_printq_list_delete(struct fast_reload_printq* list) +{ + struct fast_reload_printq* printq = list, *next; + while(printq) { + next = printq->next; + fr_printq_delete(printq); + printq = next; + } +} + +/** fast reload printq remove the item from the printq list */ +static void +fr_printq_list_remove(struct fast_reload_printq* printq) +{ + struct daemon* daemon = printq->worker->daemon; + if(printq->prev == NULL) + daemon->fast_reload_printq_list = printq->next; + else printq->prev->next = printq->next; + if(printq->next) + printq->next->prev = printq->prev; + printq->in_list = 0; +} + +/** fast reload printq, remove the printq when no longer needed, + * like the stream is closed. */ +static void +fr_printq_remove(struct fast_reload_printq* printq) +{ + if(!printq) + return; + if(printq->worker->daemon->fast_reload_thread && + printq->worker->daemon->fast_reload_thread->printq == printq) + printq->worker->daemon->fast_reload_thread->printq = NULL; + if(printq->in_list) + fr_printq_list_remove(printq); + fr_printq_delete(printq); +} + +/** fast reload thread, send stop command to the thread, from the main thread. + */ +static void +fr_send_stop(struct fast_reload_thread* fr) +{ + fr_send_cmd_to(fr, fast_reload_notification_exit, 1, 0); +} + +void +fast_reload_thread_start(RES* ssl, struct worker* worker, struct rc_state* s, + int fr_verb, int fr_nopause, int fr_drop_mesh) +{ + if(worker->daemon->fast_reload_thread) { + log_err("fast reload thread already running"); + return; + } + if(!fast_reload_thread_setup(worker, fr_verb, fr_nopause, + fr_drop_mesh)) { + if(!ssl_printf(ssl, "error could not setup thread\n")) + return; + return; + } + worker->daemon->fast_reload_thread->started = 1; + +#ifndef THREADS_DISABLED + /* Setup command listener in remote servicing thread */ + /* The listener has to be nonblocking, so the the remote servicing + * thread can continue to service DNS queries, the fast reload + * thread is going to read the config from disk and apply it. */ + /* The commpair[1] element can stay blocking, it is used by the + * fast reload thread to communicate back. The thread needs to wait + * at these times, when it has to check briefly it can use poll. */ + fd_set_nonblock(worker->daemon->fast_reload_thread->commpair[0]); + worker->daemon->fast_reload_thread->service_event = ub_event_new( + comm_base_internal(worker->base), + worker->daemon->fast_reload_thread->commpair[0], + UB_EV_READ | UB_EV_PERSIST, fast_reload_service_cb, + worker->daemon->fast_reload_thread); + if(!worker->daemon->fast_reload_thread->service_event) { + fast_reload_thread_desetup(worker->daemon->fast_reload_thread); + if(!ssl_printf(ssl, "error out of memory\n")) + return; + return; + } + if(ub_event_add(worker->daemon->fast_reload_thread->service_event, + NULL) != 0) { + fast_reload_thread_desetup(worker->daemon->fast_reload_thread); + if(!ssl_printf(ssl, "error out of memory adding service event\n")) + return; + return; + } + worker->daemon->fast_reload_thread->service_event_is_added = 1; + + /* Setup the comm point to the remote control client as an event + * on the remote servicing thread, which it already is. + * It needs a new callback to service it. */ + log_assert(s); + state_list_remove_elem(&s->rc->busy_list, s->c); + s->rc->active --; + /* Set the comm point file descriptor to nonblocking. So that + * printout to the remote control client does not block the + * server thread from servicing DNS queries. */ + fd_set_nonblock(s->c->fd); + worker->daemon->fast_reload_thread->printq = fr_printq_create(s->c, + worker); + if(!worker->daemon->fast_reload_thread->printq) { + fast_reload_thread_desetup(worker->daemon->fast_reload_thread); + if(!ssl_printf(ssl, "error out of memory create printq\n")) + return; + return; + } + worker->daemon->fast_reload_thread->printq->remote = *ssl; + s->rc = NULL; /* move away the rc state */ + /* Nothing to print right now, so no need to have it active. */ + comm_point_stop_listening(worker->daemon->fast_reload_thread->printq->client_cp); + + /* Start fast reload thread */ + ub_thread_create(&worker->daemon->fast_reload_thread->tid, + fast_reload_thread_main, worker->daemon->fast_reload_thread); +#else + (void)s; +#endif +} + +void +fast_reload_thread_stop(struct fast_reload_thread* fast_reload_thread) +{ + struct worker* worker = fast_reload_thread->worker; + if(!fast_reload_thread) + return; + fr_send_stop(fast_reload_thread); + if(worker->daemon->fast_reload_thread != NULL) { + /* If it did not exit yet, join with the thread now. It is + * going to exit because the exit command is sent to it. */ + fr_main_perform_done(fast_reload_thread); + } +} diff --git a/daemon/remote.h b/daemon/remote.h index 4902803f5..064d7b7fc 100644 --- a/daemon/remote.h +++ b/daemon/remote.h @@ -48,6 +48,7 @@ #ifdef HAVE_OPENSSL_SSL_H #include #endif +#include "util/locks.h" struct config_file; struct listen_list; struct listen_port; @@ -55,6 +56,7 @@ struct worker; struct comm_reply; struct comm_point; struct daemon_remote; +struct config_strlist_head; /** number of milliseconds timeout on incoming remote control handshake */ #define REMOTE_CONTROL_TCP_TIMEOUT 120000 @@ -118,6 +120,137 @@ struct remote_stream { }; typedef struct remote_stream RES; +/** + * Notification status. This is exchanged between the fast reload thread + * and the server thread, over the commpair sockets. + */ +enum fast_reload_notification { + /** nothing, not used */ + fast_reload_notification_none = 0, + /** the fast reload thread is done */ + fast_reload_notification_done = 1, + /** the fast reload thread is done but with an error, it failed */ + fast_reload_notification_done_error = 2, + /** the fast reload thread is told to exit by the server thread. + * Sent on server quit while the reload is running. */ + fast_reload_notification_exit = 3, + /** the fast reload thread has exited, after being told to exit */ + fast_reload_notification_exited = 4, + /** the fast reload thread has information to print out */ + fast_reload_notification_printout = 5, + /** stop as part of the reload the thread and other threads */ + fast_reload_notification_reload_stop = 6, + /** ack the stop as part of the reload, and also ack start */ + fast_reload_notification_reload_ack = 7, + /** resume from stop as part of the reload */ + fast_reload_notification_reload_start = 8, + /** the fast reload thread wants the mainthread to poll workers, + * after the reload, sent when nopause is used */ + fast_reload_notification_reload_nopause_poll = 9 +}; + +/** + * Fast reload printout queue. Contains a list of strings, that need to be + * printed over the file descriptor. + */ +struct fast_reload_printq { + /** if this item is in a list, the previous and next */ + struct fast_reload_printq *prev, *next; + /** if this item is in a list, it is true. */ + int in_list; + /** list of strings to printout */ + struct config_strlist_head* to_print; + /** the current item to print. It is malloced. NULL if none. */ + char* client_item; + /** The length, strlen, of the client_item, that has to be sent. */ + int client_len; + /** The number of bytes sent of client_item. */ + int client_byte_count; + /** the comm point for the client connection, the remote control + * client. */ + struct comm_point* client_cp; + /** the remote control connection to print output to. */ + struct remote_stream remote; + /** the worker that the event is added in */ + struct worker* worker; +}; + +/** + * Fast reload auth zone change. Keeps track if an auth zone was removed, + * added or changed. This is needed because workers can have events for + * dealing with auth zones, like transfers, and those have to be removed + * too, not just the auth zone structure from the tree. */ +struct fast_reload_auth_change { + /** next in the list of auth zone changes. */ + struct fast_reload_auth_change* next; + /** the zone in the old config */ + struct auth_zone* old_z; + /** the zone in the new config */ + struct auth_zone* new_z; + /** if the zone was deleted */ + int is_deleted; + /** if the zone was added */ + int is_added; + /** if the zone has been changed */ + int is_changed; +}; + +/** + * Fast reload thread structure + */ +struct fast_reload_thread { + /** the thread number for the dtio thread, + * must be first to cast thread arg to int* in checklock code. */ + int threadnum; + /** communication socket pair, that sends commands */ + int commpair[2]; + /** thread id, of the io thread */ + ub_thread_type tid; + /** if the io processing has started */ + int started; + /** if the thread has to quit */ + int need_to_quit; + /** verbosity of the fast_reload command, the number of +v options */ + int fr_verb; + /** option to not pause threads during reload */ + int fr_nopause; + /** option to drop mesh queries */ + int fr_drop_mesh; + + /** the event that listens on the remote service worker to the + * commpair, it receives content from the fast reload thread. */ + void* service_event; + /** if the event that listens on the remote service worker has + * been added to the comm base. */ + int service_event_is_added; + /** the service event can read a cmd, nonblocking, so it can + * save the partial read cmd here */ + uint32_t service_read_cmd; + /** the number of bytes in service_read_cmd */ + int service_read_cmd_count; + /** the worker that the service_event is added in */ + struct worker* worker; + + /** the printout of output to the remote client. */ + struct fast_reload_printq *printq; + + /** lock on fr_output, to stop race when both remote control thread + * and fast reload thread use fr_output list. */ + lock_basic_type fr_output_lock; + /** list of strings, that the fast reload thread produces that have + * to be printed. The remote control thread can pick them up with + * the lock. */ + struct config_strlist_head* fr_output; + + /** communication socket pair, to respond to the reload request */ + int commreload[2]; + + /** the list of auth zone changes. */ + struct fast_reload_auth_change* auth_zone_change_list; + /** the old tree of auth zones, to lookup. */ + struct auth_zones* old_auth_zones; +}; + /** * Create new remote control state for the daemon. * @param cfg: config file with key file settings. @@ -203,4 +336,38 @@ int ssl_printf(RES* ssl, const char* format, ...) int ssl_read_line(RES* ssl, char* buf, size_t max); #endif /* HAVE_SSL */ +/** + * Start fast reload thread + * @param ssl: the RES connection to print to. + * @param worker: the remote servicing worker. + * @param s: the rc_state that is servicing the remote control connection to + * the remote control client. It needs to be moved away to stay connected + * while the fast reload is running. + * @param fr_verb: verbosity to print output at. 0 is nothing, 1 is some + * and 2 is more detail. + * @param fr_nopause: option to not pause threads during reload. + * @param fr_drop_mesh: option to drop mesh queries. + */ +void fast_reload_thread_start(RES* ssl, struct worker* worker, + struct rc_state* s, int fr_verb, int fr_nopause, int fr_drop_mesh); + +/** + * Stop fast reload thread + * @param fast_reload_thread: the thread struct. + */ +void fast_reload_thread_stop(struct fast_reload_thread* fast_reload_thread); + +/** fast reload thread commands to remote service thread event callback */ +void fast_reload_service_cb(int fd, short bits, void* arg); + +/** fast reload callback for the remote control client connection */ +int fast_reload_client_callback(struct comm_point* c, void* arg, int err, + struct comm_reply* rep); + +/** fast reload printq delete list */ +void fast_reload_printq_list_delete(struct fast_reload_printq* list); + +/** Pick up per worker changes after a fast reload. */ +void fast_reload_worker_pickup_changes(struct worker* worker); + #endif /* DAEMON_REMOTE_H */ diff --git a/daemon/unbound.c b/daemon/unbound.c index 8de7eb0a5..8e59bb25a 100644 --- a/daemon/unbound.c +++ b/daemon/unbound.c @@ -710,6 +710,9 @@ perform_setup(struct daemon* daemon, struct config_file* cfg, int debug_mode, * it would succeed on SIGHUP as well */ if(!cfg->use_syslog) log_init(cfg->logfile, cfg->use_syslog, cfg->chrootdir); + daemon->cfgfile = strdup(*cfgfile); + if(!daemon->cfgfile) + fatal_exit("out of memory in daemon cfgfile strdup"); } /** diff --git a/daemon/worker.c b/daemon/worker.c index ba5118a74..54ba2d76c 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -371,6 +371,84 @@ worker_check_request(sldns_buffer* pkt, struct worker* worker, return; } +/** + * Send fast-reload acknowledgement to the mainthread in one byte. + * This signals that this worker has received the previous command. + * The worker is waiting if that is after a reload_stop command. + * Or the worker has briefly processed the event itself, and in doing so + * released data pointers to old config, after a reload_poll command. + */ +static void +worker_send_reload_ack(struct worker* worker) +{ + /* If this is clipped to 8 bits because thread_num>255, then that + * is not a problem, the receiver counts the number of bytes received. + * The number is informative only. */ + uint8_t c = (uint8_t)worker->thread_num; + ssize_t ret; + while(1) { + ret = send(worker->daemon->fast_reload_thread->commreload[1], + (void*)&c, 1, 0); + if(ret == -1) { + if( +#ifndef USE_WINSOCK + errno == EINTR || errno == EAGAIN +# ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +# endif +#else + WSAGetLastError() == WSAEINTR || + WSAGetLastError() == WSAEINPROGRESS || + WSAGetLastError() == WSAEWOULDBLOCK +#endif + ) + continue; /* Try again. */ + log_err("worker reload ack reply: send failed: %s", + sock_strerror(errno)); + break; + } + break; + } +} + +/** stop and wait to resume the worker */ +static void +worker_stop_and_wait(struct worker* worker) +{ + uint8_t* buf = NULL; + uint32_t len = 0, cmd; + worker_send_reload_ack(worker); + /* wait for reload */ + if(!tube_read_msg(worker->cmd, &buf, &len, 0)) { + log_err("worker reload read reply failed"); + return; + } + if(len != sizeof(uint32_t)) { + log_err("worker reload reply, bad control msg length %d", + (int)len); + free(buf); + return; + } + cmd = sldns_read_uint32(buf); + free(buf); + if(cmd == worker_cmd_quit) { + /* quit anyway */ + verbose(VERB_ALGO, "reload reply, control cmd quit"); + comm_base_exit(worker->base); + return; + } + if(cmd != worker_cmd_reload_start) { + log_err("worker reload reply, wrong reply command"); + } + if(worker->daemon->fast_reload_drop_mesh) { + verbose(VERB_ALGO, "worker: drop mesh queries after reload"); + mesh_delete_all(worker->env.mesh); + } + fast_reload_worker_pickup_changes(worker); + worker_send_reload_ack(worker); + verbose(VERB_ALGO, "worker resume after reload"); +} + void worker_handle_control_cmd(struct tube* ATTR_UNUSED(tube), uint8_t* msg, size_t len, int error, void* arg) @@ -406,6 +484,15 @@ worker_handle_control_cmd(struct tube* ATTR_UNUSED(tube), uint8_t* msg, verbose(VERB_ALGO, "got control cmd remote"); daemon_remote_exec(worker); break; + case worker_cmd_reload_stop: + verbose(VERB_ALGO, "got control cmd reload_stop"); + worker_stop_and_wait(worker); + break; + case worker_cmd_reload_poll: + verbose(VERB_ALGO, "got control cmd reload_poll"); + fast_reload_worker_pickup_changes(worker); + worker_send_reload_ack(worker); + break; default: log_err("bad command %d", (int)cmd); break; @@ -600,7 +687,8 @@ apply_respip_action(struct worker* worker, const struct query_info* qinfo, return 1; if(!respip_rewrite_reply(qinfo, cinfo, rep, encode_repp, &actinfo, - alias_rrset, 0, worker->scratchpad, az, NULL)) + alias_rrset, 0, worker->scratchpad, az, NULL, + worker->env.views, worker->env.respip_set)) return 0; /* xxx_deny actions mean dropping the reply, unless the original reply @@ -761,7 +849,8 @@ answer_from_cache(struct worker* worker, struct query_info* qinfo, } else if(partial_rep && !respip_merge_cname(partial_rep, qinfo, rep, cinfo, must_validate, &encode_rep, worker->scratchpad, - worker->env.auth_zones)) { + worker->env.auth_zones, worker->env.views, + worker->env.respip_set)) { goto bail_out; } if(encode_rep != rep) { @@ -1813,7 +1902,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error, cinfo_tmp.tag_datas = acladdr->tag_datas; cinfo_tmp.tag_datas_size = acladdr->tag_datas_size; cinfo_tmp.view = acladdr->view; - cinfo_tmp.respip_set = worker->daemon->respip_set; + cinfo_tmp.view_name = NULL; cinfo = &cinfo_tmp; } diff --git a/daemon/worker.h b/daemon/worker.h index ab2fc728d..b7bb52fd7 100644 --- a/daemon/worker.h +++ b/daemon/worker.h @@ -72,7 +72,13 @@ enum worker_commands { /** obtain statistics without statsclear */ worker_cmd_stats_noreset, /** execute remote control command */ - worker_cmd_remote + worker_cmd_remote, + /** for fast-reload, perform stop */ + worker_cmd_reload_stop, + /** for fast-reload, start again */ + worker_cmd_reload_start, + /** for fast-reload, poll to make sure worker has released data */ + worker_cmd_reload_poll }; /** diff --git a/dnstap/dnstap.c b/dnstap/dnstap.c index cff308f93..071fd0895 100644 --- a/dnstap/dnstap.c +++ b/dnstap/dnstap.c @@ -192,8 +192,11 @@ static void dt_apply_identity(struct dt_env *env, struct config_file *cfg) { char buf[MAXHOSTNAMELEN+1]; - if (!cfg->dnstap_send_identity) + if (!cfg->dnstap_send_identity) { + free(env->identity); + env->identity = NULL; return; + } free(env->identity); if (cfg->dnstap_identity == NULL || cfg->dnstap_identity[0] == 0) { if (gethostname(buf, MAXHOSTNAMELEN) == 0) { @@ -215,8 +218,11 @@ dt_apply_identity(struct dt_env *env, struct config_file *cfg) static void dt_apply_version(struct dt_env *env, struct config_file *cfg) { - if (!cfg->dnstap_send_version) + if (!cfg->dnstap_send_version) { + free(env->version); + env->version = NULL; return; + } free(env->version); if (cfg->dnstap_version == NULL || cfg->dnstap_version[0] == 0) env->version = strdup(PACKAGE_STRING); @@ -230,13 +236,8 @@ dt_apply_version(struct dt_env *env, struct config_file *cfg) } void -dt_apply_cfg(struct dt_env *env, struct config_file *cfg) +dt_apply_logcfg(struct dt_env *env, struct config_file *cfg) { - if (!cfg->dnstap) - return; - - dt_apply_identity(env, cfg); - dt_apply_version(env, cfg); if ((env->log_resolver_query_messages = (unsigned int) cfg->dnstap_log_resolver_query_messages)) { @@ -275,6 +276,17 @@ dt_apply_cfg(struct dt_env *env, struct config_file *cfg) lock_basic_unlock(&env->sample_lock); } +void +dt_apply_cfg(struct dt_env *env, struct config_file *cfg) +{ + if (!cfg->dnstap) + return; + + dt_apply_identity(env, cfg); + dt_apply_version(env, cfg); + dt_apply_logcfg(env, cfg); +} + int dt_init(struct dt_env *env, struct comm_base* base) { diff --git a/dnstap/dnstap.h b/dnstap/dnstap.h index 21c033697..4390a9cf1 100644 --- a/dnstap/dnstap.h +++ b/dnstap/dnstap.h @@ -106,6 +106,13 @@ dt_create(struct config_file* cfg); void dt_apply_cfg(struct dt_env *env, struct config_file *cfg); +/** + * Apply config settings for log enable for message types. + * @param env: dnstap environment object. + * @param cfg: new config settings. + */ +void dt_apply_logcfg(struct dt_env *env, struct config_file *cfg); + /** * Initialize per-worker state in dnstap environment object. * @param env: dnstap environment object to initialize, created with dt_create(). diff --git a/dnstap/unbound-dnstap-socket.c b/dnstap/unbound-dnstap-socket.c index cfa0c8f95..a01627de9 100644 --- a/dnstap/unbound-dnstap-socket.c +++ b/dnstap/unbound-dnstap-socket.c @@ -1787,6 +1787,20 @@ void remote_get_opt_ssl(char* ATTR_UNUSED(str), void* ATTR_UNUSED(arg)) log_assert(0); } +void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), + void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c), + void* ATTR_UNUSED(arg), int ATTR_UNUSED(error), + struct comm_reply* ATTR_UNUSED(repinfo)) +{ + log_assert(0); + return 0; +} + #ifdef HAVE_NGTCP2 void doq_client_event_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), void* ATTR_UNUSED(arg)) diff --git a/doc/Changelog b/doc/Changelog index 8a5726fe3..1bebdb7d0 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,5 +1,9 @@ 31 March 2025: Wouter - iana portlist update. + - Merge #1042: Fast Reload. The unbound-control fast_reload is added. + It reads changed config in a thread, then only briefly pauses the + service threads, that keep running. DNS service is only interrupted + briefly, less than a second. 27 March 2025: Wouter - Fix unit test dname log printout typecast. diff --git a/doc/unbound-control.8.in b/doc/unbound-control.8.in index 467374d33..22a121414 100644 --- a/doc/unbound-control.8.in +++ b/doc/unbound-control.8.in @@ -60,6 +60,93 @@ Reload the server but try to keep the RRset and message cache if That means the caches sizes and the number of threads must not change between reloads. .TP +.B fast_reload \fR[\fI+dpv\fR] +Reload the server, but keep downtime to a minimum, so that user queries +keep seeing service. This needs the code compiled with threads. The config +is loaded in a thread, and prepared, then it briefly pauses the existing +server and updates config options. The intent is that the pause does not +impact the service of user queries. The cache is kept. Also user queries +worked on are kept and continue, but with the new config options. +This command is experimental at this time. +Not all options are changed, but it changes like forwards, stubs and +local zones. Also access-control and interface-action and similar options, +also tcp-connection-limits, views. It can reload some define-tag changes. +It does not work with interface, outgoing-interface changes, also not with +remote-control, outgoing-port-permit, outgoing-port-avoid, msg-buffer-size, +slabs options and statistics-interval changes. +.IP +The fast reload also works on the options: insecure-lan-zones, domain-insecure, +trust-anchor-file, trust-anchor, trusted-key-file, auto-trust-anchor-file, +auth-zone and its options, rpz and its options, edns-strings, respip_set, +view and its options, access-control options, tcp-connection-limit, +log-identity, infra-cache-numhosts, msg-cache-size, rrset-cache-size, +key-cache-size, ratelimit-size, neg-cache-size, num-queries-per-thread, +jostle-timeout, use-caps-for-id, unwanted-reply-threshold, tls-use-sni, +outgoing-tcp-mss, ip-dscp, max-reuse-tcp-queries, tcp-reuse-timeout, +tcp-auth-query-timeout, delay-close. +.IP +For dnstap, the options can be changed: dnstap-log-resolver-query-messages, +dnstap-log-resolver-response-messages, dnstap-log-client-query-messages, +dnstap-log-client-response-messages, dnstap-log-forwarder-query-messages +and dnstap-log-forwarder-response-messages. It does not work with +these options: dnstap-enable, dnstap-bidirectional, dnstap-socket-path, +dnstap-ip, dnstap-tls, dnstap-tls-server-name, dnstap-tls-cert-bundle, +dnstap-tls-client-key-file and dnstap-tls-client-cert-file. The options +dnstap-send-identity, dnstap-send-version, dnstap-identity, and +dnstap-version can be loaded when '+p' is not used. +.IP +The '+v' option makes the output verbose which includes the time it took to do +the reload. +With '+vv' it is more verbose which includes the amount of memory that was +allocated temporarily to perform the reload; this amount of memory can be big +if the config has large contents. +In the timing output the 'reload' time is the time during which the server was +paused. +.IP +The '+p' option makes the reload not pause threads, they keep running. +Locks are acquired, but items are updated in sequence, so it is possible +for threads to see an inconsistent state with some options from the old +and some options from the new config, such as cache TTL parameters from the +old config and forwards from the new config. The stubs and forwards are +updated at the same time, so that they are viewed consistently, either old +or new values together. The option makes the reload time take eg. 3 +microseconds instead of 0.3 milliseconds during which the worker threads are +interrupted. So, the interruption is much shorter, at the expense of some +inconsistency. After the reload itself, every worker thread is briefly +contacted to make them release resources, this makes the delete timing +a little longer, and takes up time from the remote control servicing +worker thread. +.IP +With the nopause option, the reload does not work to reload some options, +that fast reload works on without the nopause option: val-bogus-ttl, +val-date-override, val-sig-key-min, val-sig-skew-max, val-max-restart, +val-nsec3-keysize-iterations, target-fetch-policy, outbound-msg-retry, +max-sent-count, max-query-restarts, do-not-query-address, +do-not-query-localhost, private-address, private-domain, caps-exempt, +nat64-prefix, do-nat64, infra-host-ttl, infra-keep-probing, ratelimit, +ip-ratelimit, ip-ratelimit-cookie, wait-limit-netblock, +wait-limit-cookie-netblock, ratelimit-below-domain, ratelimit-for-domain. +.IP +The '+d' option makes the reload drop queries that the worker threads are +working on. This is like flush_requestlist. Without it the queries are kept +so that users keep getting answers for those queries that are currently +processed. The drop makes it so that queries during the life time of the +query processing see only old, or only new config options. +.IP +When there are changes to the config tags, from \fBdefine\-tag\fR config, +then the '+d' option is implicitly turned on with a warning printout, and +queries are dropped. +This is to stop references to the old tag information, by the old +queries. If the number of tags is increased in the newly loaded config, by +adding tags at the end, then the implicit '+d' option is not needed. +.IP +For response ip, that is actions associated with IP addresses, and perhaps +intersected with access control tag and action information, those settings +are stored with a query when it comes in based on its source IP address. +The old information is kept with the query until the queries are done. +This is gone when those queries are resolved and finished, or it is possible +to flush the requestlist with '+d'. +.TP .B verbosity \fInumber Change verbosity value for logging. Same values as \fBverbosity\fR keyword in \fIunbound.conf\fR(5). This new setting lasts until the server is issued diff --git a/iterator/iter_fwd.c b/iterator/iter_fwd.c index 048d67401..5c104a0a3 100644 --- a/iterator/iter_fwd.c +++ b/iterator/iter_fwd.c @@ -624,3 +624,19 @@ forwards_delete_stub_hole(struct iter_forwards* fwd, uint16_t c, fwd_init_parents(fwd); if(!nolock) { lock_rw_unlock(&fwd->lock); } } + +void +forwards_swap_tree(struct iter_forwards* fwd, struct iter_forwards* data) +{ + rbtree_type* oldtree = fwd->tree; + if(oldtree) { + lock_unprotect(&fwd->lock, oldtree); + } + if(data->tree) { + lock_unprotect(&data->lock, data->tree); + } + fwd->tree = data->tree; + data->tree = oldtree; + lock_protect(&fwd->lock, fwd->tree, sizeof(*fwd->tree)); + lock_protect(&data->lock, data->tree, sizeof(*data->tree)); +} diff --git a/iterator/iter_fwd.h b/iterator/iter_fwd.h index 4527d899c..095cd96df 100644 --- a/iterator/iter_fwd.h +++ b/iterator/iter_fwd.h @@ -234,4 +234,13 @@ int forwards_add_stub_hole(struct iter_forwards* fwd, uint16_t c, void forwards_delete_stub_hole(struct iter_forwards* fwd, uint16_t c, uint8_t* nm, int nolock); +/** + * Swap internal tree with preallocated entries. Caller should manage + * the locks. + * @param fwd: the forward data structure. + * @param data: the data structure used to take elements from. This contains + * the old elements on return. + */ +void forwards_swap_tree(struct iter_forwards* fwd, struct iter_forwards* data); + #endif /* ITERATOR_ITER_FWD_H */ diff --git a/iterator/iter_hints.c b/iterator/iter_hints.c index fb9d10413..9faf155ab 100644 --- a/iterator/iter_hints.c +++ b/iterator/iter_hints.c @@ -611,3 +611,14 @@ hints_delete_stub(struct iter_hints* hints, uint16_t c, uint8_t* nm, name_tree_init_parents(&hints->tree); if(!nolock) { lock_rw_unlock(&hints->lock); } } + +void +hints_swap_tree(struct iter_hints* hints, struct iter_hints* data) +{ + rbnode_type* oldroot = hints->tree.root; + size_t oldcount = hints->tree.count; + hints->tree.root = data->tree.root; + hints->tree.count = data->tree.count; + data->tree.root = oldroot; + data->tree.count = oldcount; +} diff --git a/iterator/iter_hints.h b/iterator/iter_hints.h index 26de323c9..87434b5ac 100644 --- a/iterator/iter_hints.h +++ b/iterator/iter_hints.h @@ -198,4 +198,13 @@ int hints_add_stub(struct iter_hints* hints, uint16_t c, struct delegpt* dp, void hints_delete_stub(struct iter_hints* hints, uint16_t c, uint8_t* nm, int nolock); +/** + * Swap internal tree with preallocated entries. Caller should manage + * the locks. + * @param hints: the hints data structure. + * @param data: the data structure used to take elements from. This contains + * the old elements on return. + */ +void hints_swap_tree(struct iter_hints* hints, struct iter_hints* data); + #endif /* ITERATOR_ITER_HINTS_H */ diff --git a/iterator/iter_utils.c b/iterator/iter_utils.c index 5acd7a156..1da21896c 100644 --- a/iterator/iter_utils.c +++ b/iterator/iter_utils.c @@ -77,41 +77,73 @@ static const char DEFAULT_NAT64_PREFIX[] = "64:ff9b::/96"; /** fillup fetch policy array */ -static void -fetch_fill(struct iter_env* ie, const char* str) +static int +fetch_fill(int* target_fetch_policy, int max_dependency_depth, const char* str) { char* s = (char*)str, *e; int i; - for(i=0; imax_dependency_depth+1; i++) { - ie->target_fetch_policy[i] = strtol(s, &e, 10); - if(s == e) - fatal_exit("cannot parse fetch policy number %s", s); + for(i=0; imax_dependency_depth = count - 1; - ie->target_fetch_policy = (int*)calloc( - (size_t)ie->max_dependency_depth+1, sizeof(int)); - if(!ie->target_fetch_policy) { + *max_dependency_depth = count - 1; + *target_fetch_policy = (int*)calloc( + (size_t)(*max_dependency_depth)+1, sizeof(int)); + if(!*target_fetch_policy) { log_err("alloc fetch policy: out of memory"); return 0; } - fetch_fill(ie, str); + if(!fetch_fill(*target_fetch_policy, *max_dependency_depth, str)) + return 0; return 1; } -/** apply config caps whitelist items to name tree */ -static int +struct rbtree_type* +caps_white_create(void) +{ + struct rbtree_type* caps_white = rbtree_create(name_tree_compare); + if(!caps_white) + log_err("out of memory"); + return caps_white; +} + +/** delete caps_whitelist element */ +static void +caps_free(struct rbnode_type* n, void* ATTR_UNUSED(d)) +{ + if(n) { + free(((struct name_tree_node*)n)->name); + free(n); + } +} + +void +caps_white_delete(struct rbtree_type* caps_white) +{ + if(!caps_white) + return; + traverse_postorder(caps_white, caps_free, NULL); + free(caps_white); +} + +int caps_white_apply_cfg(rbtree_type* ntree, struct config_file* cfg) { struct config_strlist* p; @@ -145,12 +177,41 @@ caps_white_apply_cfg(rbtree_type* ntree, struct config_file* cfg) } int -iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg) +nat64_apply_cfg(struct iter_nat64* nat64, struct config_file* cfg) { const char *nat64_prefix; + + nat64_prefix = cfg->nat64_prefix; + if(!nat64_prefix) + nat64_prefix = cfg->dns64_prefix; + if(!nat64_prefix) + nat64_prefix = DEFAULT_NAT64_PREFIX; + if(!netblockstrtoaddr(nat64_prefix, 0, &nat64->nat64_prefix_addr, + &nat64->nat64_prefix_addrlen, &nat64->nat64_prefix_net)) { + log_err("cannot parse nat64-prefix netblock: %s", nat64_prefix); + return 0; + } + if(!addr_is_ip6(&nat64->nat64_prefix_addr, + nat64->nat64_prefix_addrlen)) { + log_err("nat64-prefix is not IPv6: %s", cfg->nat64_prefix); + return 0; + } + if(!prefixnet_is_nat64(nat64->nat64_prefix_net)) { + log_err("nat64-prefix length it not 32, 40, 48, 56, 64 or 96: %s", + nat64_prefix); + return 0; + } + nat64->use_nat64 = cfg->do_nat64; + return 1; +} + +int +iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg) +{ int i; /* target fetch policy */ - if(!read_fetch_policy(iter_env, cfg->target_fetch_policy)) + if(!read_fetch_policy(&iter_env->target_fetch_policy, + &iter_env->max_dependency_depth, cfg->target_fetch_policy)) return 0; for(i=0; imax_dependency_depth+1; i++) verbose(VERB_QUERY, "target fetch policy for level %d is %d", @@ -170,7 +231,7 @@ iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg) } if(cfg->caps_whitelist) { if(!iter_env->caps_white) - iter_env->caps_white = rbtree_create(name_tree_compare); + iter_env->caps_white = caps_white_create(); if(!iter_env->caps_white || !caps_white_apply_cfg( iter_env->caps_white, cfg)) { log_err("Could not set capsforid whitelist"); @@ -179,31 +240,13 @@ iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg) } - nat64_prefix = cfg->nat64_prefix; - if(!nat64_prefix) - nat64_prefix = cfg->dns64_prefix; - if(!nat64_prefix) - nat64_prefix = DEFAULT_NAT64_PREFIX; - if(!netblockstrtoaddr(nat64_prefix, 0, &iter_env->nat64_prefix_addr, - &iter_env->nat64_prefix_addrlen, - &iter_env->nat64_prefix_net)) { - log_err("cannot parse nat64-prefix netblock: %s", nat64_prefix); - return 0; - } - if(!addr_is_ip6(&iter_env->nat64_prefix_addr, - iter_env->nat64_prefix_addrlen)) { - log_err("nat64-prefix is not IPv6: %s", cfg->nat64_prefix); - return 0; - } - if(!prefixnet_is_nat64(iter_env->nat64_prefix_net)) { - log_err("nat64-prefix length it not 32, 40, 48, 56, 64 or 96: %s", - nat64_prefix); + if(!nat64_apply_cfg(&iter_env->nat64, cfg)) { + log_err("Could not setup nat64"); return 0; } iter_env->supports_ipv6 = cfg->do_ip6; iter_env->supports_ipv4 = cfg->do_ip4; - iter_env->use_nat64 = cfg->do_nat64; iter_env->outbound_msg_retry = cfg->outbound_msg_retry; iter_env->max_sent_count = cfg->max_sent_count; iter_env->max_query_restarts = cfg->max_query_restarts; @@ -270,7 +313,7 @@ iter_filter_unsuitable(struct iter_env* iter_env, struct module_env* env, if(!iter_env->supports_ipv6 && addr_is_ip6(&a->addr, a->addrlen)) { return -1; /* there is no ip6 available */ } - if(!iter_env->supports_ipv4 && !iter_env->use_nat64 && + if(!iter_env->supports_ipv4 && !iter_env->nat64.use_nat64 && !addr_is_ip6(&a->addr, a->addrlen)) { return -1; /* there is no ip4 available */ } diff --git a/iterator/iter_utils.h b/iterator/iter_utils.h index 30b3dbe5f..b17b091e6 100644 --- a/iterator/iter_utils.h +++ b/iterator/iter_utils.h @@ -61,6 +61,7 @@ struct sock_list; struct ub_packed_rrset_key; struct module_stack; struct outside_network; +struct iter_nat64; /* max number of lookups in the cache for target nameserver names. * This stops, for large delegations, N*N lookups in the cache. */ @@ -430,6 +431,43 @@ int iter_stub_fwd_no_cache(struct module_qstate *qstate, void iterator_set_ip46_support(struct module_stack* mods, struct module_env* env, struct outside_network* outnet); +/** + * Read config string that represents the target fetch policy. + * @param target_fetch_policy: alloced on return. + * @param max_dependency_depth: set on return. + * @param str: the config string + * @return false on failure. + */ +int read_fetch_policy(int** target_fetch_policy, int* max_dependency_depth, + const char* str); + +/** + * Create caps exempt data structure. + * @return NULL on failure. + */ +struct rbtree_type* caps_white_create(void); + +/** + * Delete caps exempt data structure. + * @param caps_white: caps exempt tree. + */ +void caps_white_delete(struct rbtree_type* caps_white); + +/** + * Apply config caps whitelist items to name tree + * @param ntree: caps exempt tree. + * @param cfg: config with options. + */ +int caps_white_apply_cfg(struct rbtree_type* ntree, struct config_file* cfg); + +/** + * Apply config for nat64 + * @param nat64: the nat64 state. + * @param cfg: config with options. + * @return false on failure. + */ +int nat64_apply_cfg(struct iter_nat64* nat64, struct config_file* cfg); + /** * Limit NSEC and NSEC3 TTL in response, RFC9077 * @param msg: dns message, the SOA record ttl is used to restrict ttls diff --git a/iterator/iterator.c b/iterator/iterator.c index a0ddb296b..8c0703e9e 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -107,16 +107,6 @@ iter_init(struct module_env* env, int id) return 1; } -/** delete caps_whitelist element */ -static void -caps_free(struct rbnode_type* n, void* ATTR_UNUSED(d)) -{ - if(n) { - free(((struct name_tree_node*)n)->name); - free(n); - } -} - void iter_deinit(struct module_env* env, int id) { @@ -128,10 +118,7 @@ iter_deinit(struct module_env* env, int id) free(iter_env->target_fetch_policy); priv_delete(iter_env->priv); donotq_delete(iter_env->donotq); - if(iter_env->caps_white) { - traverse_postorder(iter_env->caps_white, caps_free, NULL); - free(iter_env->caps_white); - } + caps_white_delete(iter_env->caps_white); free(iter_env); env->modinfo[id] = NULL; } @@ -260,7 +247,7 @@ error_supers(struct module_qstate* qstate, int id, struct module_qstate* super) log_err("out of memory adding missing"); } delegpt_mark_neg(dpns, qstate->qinfo.qtype); - if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) && + if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->nat64.use_nat64)) && (dpns->got6 == 2 || !ie->supports_ipv6)) { dpns->resolved = 1; /* mark as failed */ target_count_increase_nx(super_iq, 1); @@ -1725,7 +1712,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, */ if(iter_dp_is_useless(&qstate->qinfo, qstate->query_flags, iq->dp, ie->supports_ipv4, ie->supports_ipv6, - ie->use_nat64)) { + ie->nat64.use_nat64)) { int have_dp = 0; if(!can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, iq->qchase.qclass, &have_dp, &iq->dp, qstate->region)) { if(have_dp) { @@ -2089,7 +2076,7 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, if(mesh_jostle_exceeded(qstate->env->mesh)) { /* If no ip4 query is possible, that makes * this ns resolved. */ - if(!((ie->supports_ipv4 || ie->use_nat64) && + if(!((ie->supports_ipv4 || ie->nat64.use_nat64) && ((ns->lame && !ns->done_pside4) || (!ns->lame && !ns->got4)))) { ns->resolved = 1; @@ -2098,7 +2085,7 @@ query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, } } /* Send the A request. */ - if((ie->supports_ipv4 || ie->use_nat64) && + if((ie->supports_ipv4 || ie->nat64.use_nat64) && ((ns->lame && !ns->done_pside4) || (!ns->lame && !ns->got4))) { if(!generate_target_query(qstate, iq, id, @@ -2270,14 +2257,14 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, /* if this nameserver is at a delegation point, but that * delegation point is a stub and we cannot go higher, skip*/ if( ((ie->supports_ipv6 && !ns->done_pside6) || - ((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4)) && + ((ie->supports_ipv4 || ie->nat64.use_nat64) && !ns->done_pside4)) && !can_have_last_resort(qstate->env, ns->name, ns->namelen, iq->qchase.qclass, NULL, NULL, NULL)) { log_nametypeclass(VERB_ALGO, "cannot pside lookup ns " "because it is also a stub/forward,", ns->name, LDNS_RR_TYPE_NS, iq->qchase.qclass); if(ie->supports_ipv6) ns->done_pside6 = 1; - if(ie->supports_ipv4 || ie->use_nat64) ns->done_pside4 = 1; + if(ie->supports_ipv4 || ie->nat64.use_nat64) ns->done_pside4 = 1; continue; } /* query for parent-side A and AAAA for nameservers */ @@ -2302,7 +2289,7 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, return 0; } } - if((ie->supports_ipv4 || ie->use_nat64) && !ns->done_pside4) { + if((ie->supports_ipv4 || ie->nat64.use_nat64) && !ns->done_pside4) { /* Send the A request. */ if(!generate_parentside_target_query(qstate, iq, id, ns->name, ns->namelen, @@ -2571,7 +2558,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, } if(!ie->supports_ipv6) delegpt_no_ipv6(iq->dp); - if(!ie->supports_ipv4 && !ie->use_nat64) + if(!ie->supports_ipv4 && !ie->nat64.use_nat64) delegpt_no_ipv4(iq->dp); delegpt_log(VERB_ALGO, iq->dp); @@ -3070,9 +3057,9 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, real_addr = target->addr; real_addrlen = target->addrlen; - if(ie->use_nat64 && target->addr.ss_family == AF_INET) { - addr_to_nat64(&target->addr, &ie->nat64_prefix_addr, - ie->nat64_prefix_addrlen, ie->nat64_prefix_net, + if(ie->nat64.use_nat64 && target->addr.ss_family == AF_INET) { + addr_to_nat64(&target->addr, &ie->nat64.nat64_prefix_addr, + ie->nat64.nat64_prefix_addrlen, ie->nat64.nat64_prefix_net, &real_addr, &real_addrlen); log_name_addr(VERB_QUERY, "applied NAT64:", iq->dp->name, &real_addr, real_addrlen); @@ -3882,7 +3869,7 @@ processTargetResponse(struct module_qstate* qstate, int id, } else { verbose(VERB_ALGO, "iterator TargetResponse failed"); delegpt_mark_neg(dpns, qstate->qinfo.qtype); - if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->use_nat64)) && + if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->nat64.use_nat64)) && (dpns->got6 == 2 || !ie->supports_ipv6)) { dpns->resolved = 1; /* fail the target */ /* do not count cached answers */ diff --git a/iterator/iterator.h b/iterator/iterator.h index 46701f6ee..ae4b4e451 100644 --- a/iterator/iterator.h +++ b/iterator/iterator.h @@ -46,8 +46,6 @@ #include "util/data/msgreply.h" #include "util/module.h" struct delegpt; -struct iter_hints; -struct iter_forwards; struct iter_donotq; struct iter_prep_list; struct iter_priv; @@ -108,15 +106,9 @@ extern int BLACKLIST_PENALTY; #define EMPTY_NODATA_RETRY_COUNT 2 /** - * Global state for the iterator. + * Iterator global state for nat64. */ -struct iter_env { - /** A flag to indicate whether or not we have an IPv6 route */ - int supports_ipv6; - - /** A flag to indicate whether or not we have an IPv4 route */ - int supports_ipv4; - +struct iter_nat64 { /** A flag to locally apply NAT64 to make IPv4 addrs into IPv6 */ int use_nat64; @@ -128,6 +120,20 @@ struct iter_env { /** CIDR mask length of NAT64 prefix */ int nat64_prefix_net; +}; + +/** + * Global state for the iterator. + */ +struct iter_env { + /** A flag to indicate whether or not we have an IPv6 route */ + int supports_ipv6; + + /** A flag to indicate whether or not we have an IPv4 route */ + int supports_ipv4; + + /** State for nat64 */ + struct iter_nat64 nat64; /** A set of inetaddrs that should never be queried. */ struct iter_donotq* donotq; diff --git a/libunbound/libworker.c b/libunbound/libworker.c index 6b2bf7a3c..f0496452b 100644 --- a/libunbound/libworker.c +++ b/libunbound/libworker.c @@ -1059,6 +1059,20 @@ void dtio_mainfdcallback(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), } #endif +void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), + void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c), + void* ATTR_UNUSED(arg), int ATTR_UNUSED(error), + struct comm_reply* ATTR_UNUSED(repinfo)) +{ + log_assert(0); + return 0; +} + #ifdef HAVE_NGTCP2 void doq_client_event_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), void* ATTR_UNUSED(arg)) diff --git a/respip/respip.c b/respip/respip.c index c0ecba160..353a0fd78 100644 --- a/respip/respip.c +++ b/respip/respip.c @@ -105,6 +105,7 @@ respip_sockaddr_find_or_create(struct respip_set* set, struct sockaddr_storage* socklen_t addrlen, int net, int create, const char* ipstr) { struct resp_addr* node; + log_assert(set); node = (struct resp_addr*)addr_tree_find(&set->ip_tree, addr, addrlen, net); if(!node && create) { node = regional_alloc_zero(set->region, sizeof(*node)); @@ -128,6 +129,7 @@ void respip_sockaddr_delete(struct respip_set* set, struct resp_addr* node) { struct resp_addr* prev; + log_assert(set); prev = (struct resp_addr*)rbtree_previous((struct rbnode_type*)node); lock_rw_destroy(&node->lock); (void)rbtree_delete(&set->ip_tree, node); @@ -146,6 +148,7 @@ respip_find_or_create(struct respip_set* set, const char* ipstr, int create) struct sockaddr_storage addr; int net; socklen_t addrlen; + log_assert(set); if(!netblockstrtoaddr(ipstr, 0, &addr, &addrlen, &net)) { log_err("cannot parse netblock: '%s'", ipstr); @@ -160,6 +163,7 @@ respip_tag_cfg(struct respip_set* set, const char* ipstr, const uint8_t* taglist, size_t taglen) { struct resp_addr* node; + log_assert(set); if(!(node=respip_find_or_create(set, ipstr, 1))) return 0; @@ -183,6 +187,7 @@ respip_action_cfg(struct respip_set* set, const char* ipstr, { struct resp_addr* node; enum respip_action action; + log_assert(set); if(!(node=respip_find_or_create(set, ipstr, 1))) return 0; @@ -325,6 +330,7 @@ static int respip_data_cfg(struct respip_set* set, const char* ipstr, const char* rrstr) { struct resp_addr* node; + log_assert(set); node=respip_find_or_create(set, ipstr, 0); if(!node || node->action == respip_none) { @@ -344,6 +350,7 @@ respip_set_apply_cfg(struct respip_set* set, char* const* tagname, int num_tags, struct config_strbytelist* p; struct config_str2list* pa; struct config_str2list* pd; + log_assert(set); set->tagname = tagname; set->num_tags = num_tags; @@ -609,6 +616,7 @@ respip_addr_lookup(const struct reply_info *rep, struct respip_set* rs, struct resp_addr* ra; struct sockaddr_storage ss; socklen_t addrlen; + log_assert(rs); lock_rw_rdlock(&rs->lock); for(i=0; ian_numrrsets; i++) { @@ -867,7 +875,8 @@ respip_rewrite_reply(const struct query_info* qinfo, const struct respip_client_info* cinfo, const struct reply_info* rep, struct reply_info** new_repp, struct respip_action_info* actinfo, struct ub_packed_rrset_key** alias_rrset, int search_only, - struct regional* region, struct auth_zones* az, int* rpz_passthru) + struct regional* region, struct auth_zones* az, int* rpz_passthru, + struct views* views, struct respip_set* ipset) { const uint8_t* ctaglist; size_t ctaglen; @@ -876,7 +885,6 @@ respip_rewrite_reply(const struct query_info* qinfo, struct config_strlist** tag_datas; size_t tag_datas_size; struct view* view = NULL; - struct respip_set* ipset = NULL; size_t rrset_id = 0, rr_id = 0; enum respip_action action = respip_none; int tag = -1; @@ -899,8 +907,20 @@ respip_rewrite_reply(const struct query_info* qinfo, tag_actions_size = cinfo->tag_actions_size; tag_datas = cinfo->tag_datas; tag_datas_size = cinfo->tag_datas_size; - view = cinfo->view; - ipset = cinfo->respip_set; + if(cinfo->view) { + view = cinfo->view; + lock_rw_rdlock(&view->lock); + } else if(cinfo->view_name) { + view = views_find_view(views, cinfo->view_name, 0); + if(!view) { + /* If the view no longer exists, the rewrite can not + * be processed further. */ + verbose(VERB_ALGO, "respip: failed because view %s no " + "longer exists", cinfo->view_name); + return 0; + } + /* The view is rdlocked by views_find_view. */ + } log_assert(ipset); @@ -915,7 +935,6 @@ respip_rewrite_reply(const struct query_info* qinfo, * Note also that we assume 'view' is valid in this function, which * should be safe (see unbound bug #1191) */ if(view) { - lock_rw_rdlock(&view->lock); if(view->respip_set) { if((raddr = respip_addr_lookup(rep, view->respip_set, &rrset_id, &rr_id))) { @@ -1101,7 +1120,8 @@ respip_operate(struct module_qstate* qstate, enum module_ev event, int id, qstate->client_info, qstate->return_msg->rep, &new_rep, &actinfo, &alias_rrset, 0, qstate->region, qstate->env->auth_zones, - &qstate->rpz_passthru)) { + &qstate->rpz_passthru, qstate->env->views, + qstate->env->respip_set)) { goto servfail; } if(actinfo.action != respip_none) { @@ -1149,7 +1169,8 @@ respip_merge_cname(struct reply_info* base_rep, const struct query_info* qinfo, const struct reply_info* tgt_rep, const struct respip_client_info* cinfo, int must_validate, struct reply_info** new_repp, struct regional* region, - struct auth_zones* az) + struct auth_zones* az, struct views* views, + struct respip_set* respip_set) { struct reply_info* new_rep; struct reply_info* tmp_rep = NULL; /* just a placeholder */ @@ -1176,7 +1197,7 @@ respip_merge_cname(struct reply_info* base_rep, /* see if the target reply would be subject to a response-ip action. */ if(!respip_rewrite_reply(qinfo, cinfo, tgt_rep, &tmp_rep, &actinfo, - &alias_rrset, 1, region, az, NULL)) + &alias_rrset, 1, region, az, NULL, views, respip_set)) return 0; if(actinfo.action != respip_none) { log_info("CNAME target of redirect response-ip action would " @@ -1229,7 +1250,8 @@ respip_inform_super(struct module_qstate* qstate, int id, if(!respip_merge_cname(super->return_msg->rep, &qstate->qinfo, qstate->return_msg->rep, super->client_info, super->env->need_to_validate, &new_rep, super->region, - qstate->env->auth_zones)) + qstate->env->auth_zones, qstate->env->views, + qstate->env->respip_set)) goto fail; super->return_msg->rep = new_rep; return; @@ -1326,3 +1348,35 @@ respip_inform_print(struct respip_action_info* respip_actinfo, uint8_t* qname, (actionstr) ? actionstr : "inform", srcip, port); log_nametypeclass(NO_VERBOSE, txt, qname, qtype, qclass); } + +size_t respip_set_get_mem(struct respip_set* set) +{ + size_t m; + if(!set) return 0; + m = sizeof(*set); + lock_rw_rdlock(&set->lock); + m += regional_get_mem(set->region); + lock_rw_unlock(&set->lock); + return m; +} + +void +respip_set_swap_tree(struct respip_set* respip_set, + struct respip_set* data) +{ + rbnode_type* oldroot = respip_set->ip_tree.root; + size_t oldcount = respip_set->ip_tree.count; + struct regional* oldregion = respip_set->region; + char* const* oldtagname = respip_set->tagname; + int oldnum_tags = respip_set->num_tags; + respip_set->ip_tree.root = data->ip_tree.root; + respip_set->ip_tree.count = data->ip_tree.count; + respip_set->region = data->region; + respip_set->tagname = data->tagname; + respip_set->num_tags = data->num_tags; + data->ip_tree.root = oldroot; + data->ip_tree.count = oldcount; + data->region = oldregion; + data->tagname = oldtagname; + data->num_tags = oldnum_tags; +} diff --git a/respip/respip.h b/respip/respip.h index e4ab5cc9c..6469854c5 100644 --- a/respip/respip.h +++ b/respip/respip.h @@ -23,7 +23,8 @@ struct respip_set { struct regional* region; struct rbtree_type ip_tree; - lock_rw_type lock; /* lock on the respip tree */ + lock_rw_type lock; /* lock on the respip tree. It is ordered + after views and before hints, stubs and local zones. */ char* const* tagname; /* shallow copy of tag names, for logging */ int num_tags; /* number of tagname entries */ }; @@ -59,7 +60,6 @@ struct respip_addr_info; * This is essentially a subset of acl_addr (except for respip_set) but * defined as a separate structure to avoid dependency on the daemon-specific * structure. - * respip_set is supposed to refer to the response-ip set for the global view. */ struct respip_client_info { uint8_t* taglist; @@ -68,8 +68,12 @@ struct respip_client_info { size_t tag_actions_size; struct config_strlist** tag_datas; size_t tag_datas_size; + /** The view for the action, during cache callback that is by + * pointer. */ struct view* view; - struct respip_set* respip_set; + /** If from module query state, the view pointer is NULL, but the + * name is stored in reference to the view. */ + char* view_name; }; /** @@ -149,13 +153,16 @@ int respip_views_apply_cfg(struct views* vs, struct config_file* cfg, * on error. * @param region: allocator to build *new_repp. * @param az: auth zones containing RPZ information. + * @param views: views tree to lookup view used. + * @param respip_set: the respip set for the global view. * @return 1 on success, 0 on error. */ int respip_merge_cname(struct reply_info* base_rep, const struct query_info* qinfo, const struct reply_info* tgt_rep, const struct respip_client_info* cinfo, int must_validate, struct reply_info** new_repp, struct regional* region, - struct auth_zones* az); + struct auth_zones* az, struct views* views, + struct respip_set* respip_set); /** * See if any IP-based action should apply to any IP address of AAAA/A answer @@ -178,6 +185,8 @@ int respip_merge_cname(struct reply_info* base_rep, * @param region: allocator to build *new_repp. * @param rpz_passthru: keeps track of query state can have passthru that * stops further rpz processing. Or NULL for cached answer processing. + * @param views: views tree to lookup view used. + * @param ipset: the respip set for the global view. * @return 1 on success, 0 on error. */ int respip_rewrite_reply(const struct query_info* qinfo, @@ -186,7 +195,7 @@ int respip_rewrite_reply(const struct query_info* qinfo, struct respip_action_info* actinfo, struct ub_packed_rrset_key** alias_rrset, int search_only, struct regional* region, struct auth_zones* az, - int* rpz_passthru); + int* rpz_passthru, struct views* views, struct respip_set* ipset); /** * Get the response-ip function block. @@ -302,4 +311,18 @@ respip_sockaddr_delete(struct respip_set* set, struct resp_addr* node); struct ub_packed_rrset_key* respip_copy_rrset(const struct ub_packed_rrset_key* key, struct regional* region); + +/** Get memory usage of respip set tree. The routine locks and unlocks the + * set for reading. */ +size_t respip_set_get_mem(struct respip_set* set); + +/** + * Swap internal tree with preallocated entries. Caller should manage + * the locks. + * @param respip_set: response ip tree + * @param data: preallocated information. + */ +void respip_set_swap_tree(struct respip_set* respip_set, + struct respip_set* data); + #endif /* RESPIP_RESPIP_H */ diff --git a/services/authzone.c b/services/authzone.c index 90fd0235e..3c3dc9ad0 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -2317,9 +2317,6 @@ auth_free_masters(struct auth_master* list) } } -/** delete auth xfer structure - * @param xfr: delete this xfer and its tasks. - */ void auth_xfer_delete(struct auth_xfer* xfr) { @@ -7006,6 +7003,18 @@ xfr_set_timeout(struct auth_xfer* xfr, struct module_env* env, comm_timer_set(xfr->task_nextprobe->timer, &tv); } +void auth_xfer_pickup_initial_zone(struct auth_xfer* x, struct module_env* env) +{ + /* set lease_time, because we now have timestamp in env, + * (not earlier during startup and apply_cfg), and this + * notes the start time when the data was acquired */ + if(x->have_zone) + x->lease_time = *env->now; + if(x->task_nextprobe && x->task_nextprobe->worker == NULL) { + xfr_set_timeout(x, env, 0, 1); + } +} + /** initial pick up of worker timeouts, ties events to worker event loop */ void auth_xfer_pickup_initial(struct auth_zones* az, struct module_env* env) @@ -7014,14 +7023,7 @@ auth_xfer_pickup_initial(struct auth_zones* az, struct module_env* env) lock_rw_wrlock(&az->lock); RBTREE_FOR(x, struct auth_xfer*, &az->xtree) { lock_basic_lock(&x->lock); - /* set lease_time, because we now have timestamp in env, - * (not earlier during startup and apply_cfg), and this - * notes the start time when the data was acquired */ - if(x->have_zone) - x->lease_time = *env->now; - if(x->task_nextprobe && x->task_nextprobe->worker == NULL) { - xfr_set_timeout(x, env, 0, 1); - } + auth_xfer_pickup_initial_zone(x, env); lock_basic_unlock(&x->lock); } lock_rw_unlock(&az->lock); @@ -8580,3 +8582,161 @@ void auth_zones_pickup_zonemd_verify(struct auth_zones* az, } lock_rw_unlock(&az->lock); } + +/** Get memory usage of auth rrset */ +static size_t +auth_rrset_get_mem(struct auth_rrset* rrset) +{ + size_t m = sizeof(*rrset) + packed_rrset_sizeof(rrset->data); + return m; +} + +/** Get memory usage of auth data */ +static size_t +auth_data_get_mem(struct auth_data* node) +{ + size_t m = sizeof(*node) + node->namelen; + struct auth_rrset* rrset; + for(rrset = node->rrsets; rrset; rrset = rrset->next) { + m += auth_rrset_get_mem(rrset); + } + return m; +} + +/** Get memory usage of auth zone */ +static size_t +auth_zone_get_mem(struct auth_zone* z) +{ + size_t m = sizeof(*z) + z->namelen; + struct auth_data* node; + if(z->zonefile) + m += strlen(z->zonefile)+1; + RBTREE_FOR(node, struct auth_data*, &z->data) { + m += auth_data_get_mem(node); + } + if(z->rpz) + m += rpz_get_mem(z->rpz); + return m; +} + +/** Get memory usage of list of auth addr */ +static size_t +auth_addrs_get_mem(struct auth_addr* list) +{ + size_t m = 0; + struct auth_addr* a; + for(a = list; a; a = a->next) { + m += sizeof(*a); + } + return m; +} + +/** Get memory usage of list of primaries for auth xfer */ +static size_t +auth_primaries_get_mem(struct auth_master* list) +{ + size_t m = 0; + struct auth_master* n; + for(n = list; n; n = n->next) { + m += sizeof(*n); + m += auth_addrs_get_mem(n->list); + if(n->host) + m += strlen(n->host)+1; + if(n->file) + m += strlen(n->file)+1; + } + return m; +} + +/** Get memory usage or list of auth chunks */ +static size_t +auth_chunks_get_mem(struct auth_chunk* list) +{ + size_t m = 0; + struct auth_chunk* chunk; + for(chunk = list; chunk; chunk = chunk->next) { + m += sizeof(*chunk) + chunk->len; + } + return m; +} + +/** Get memory usage of auth xfer */ +static size_t +auth_xfer_get_mem(struct auth_xfer* xfr) +{ + size_t m = sizeof(*xfr) + xfr->namelen; + + /* auth_nextprobe */ + m += comm_timer_get_mem(xfr->task_nextprobe->timer); + + /* auth_probe */ + m += auth_primaries_get_mem(xfr->task_probe->masters); + m += comm_point_get_mem(xfr->task_probe->cp); + m += comm_timer_get_mem(xfr->task_probe->timer); + + /* auth_transfer */ + m += auth_chunks_get_mem(xfr->task_transfer->chunks_first); + m += auth_primaries_get_mem(xfr->task_transfer->masters); + m += comm_point_get_mem(xfr->task_transfer->cp); + m += comm_timer_get_mem(xfr->task_transfer->timer); + + /* allow_notify_list */ + m += auth_primaries_get_mem(xfr->allow_notify_list); + + return m; +} + +/** Get memory usage of auth zones ztree */ +static size_t +az_ztree_get_mem(struct auth_zones* az) +{ + size_t m = 0; + struct auth_zone* z; + RBTREE_FOR(z, struct auth_zone*, &az->ztree) { + lock_rw_rdlock(&z->lock); + m += auth_zone_get_mem(z); + lock_rw_unlock(&z->lock); + } + return m; +} + +/** Get memory usage of auth zones xtree */ +static size_t +az_xtree_get_mem(struct auth_zones* az) +{ + size_t m = 0; + struct auth_xfer* xfr; + RBTREE_FOR(xfr, struct auth_xfer*, &az->xtree) { + lock_basic_lock(&xfr->lock); + m += auth_xfer_get_mem(xfr); + lock_basic_unlock(&xfr->lock); + } + return m; +} + +size_t auth_zones_get_mem(struct auth_zones* zones) +{ + size_t m; + if(!zones) return 0; + m = sizeof(*zones); + lock_rw_rdlock(&zones->rpz_lock); + lock_rw_rdlock(&zones->lock); + m += az_ztree_get_mem(zones); + m += az_xtree_get_mem(zones); + lock_rw_unlock(&zones->lock); + lock_rw_unlock(&zones->rpz_lock); + return m; +} + +void xfr_disown_tasks(struct auth_xfer* xfr, struct worker* worker) +{ + if(xfr->task_nextprobe->worker == worker) { + xfr_nextprobe_disown(xfr); + } + if(xfr->task_probe->worker == worker) { + xfr_probe_disown(xfr); + } + if(xfr->task_transfer->worker == worker) { + xfr_transfer_disown(xfr); + } +} diff --git a/services/authzone.h b/services/authzone.h index 3994a4ead..722781a06 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -70,7 +70,8 @@ struct auth_chunk; * Authoritative zones, shared. */ struct auth_zones { - /** lock on the authzone trees */ + /** lock on the authzone trees. It is locked after views, respip, + * local_zones and before fwds and stubs. */ lock_rw_type lock; /** rbtree of struct auth_zone */ rbtree_type ztree; @@ -207,7 +208,9 @@ struct auth_xfer { * one of the tasks. * Once it has the task assigned to it, the worker can access the * other elements of the task structure without a lock, because that - * is necessary for the eventloop and callbacks from that. */ + * is necessary for the eventloop and callbacks from that. + * The auth_zone->lock is locked before this lock. + */ lock_basic_type lock; /** zone name, in uncompressed wireformat */ @@ -783,4 +786,33 @@ void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode, void auth_zones_pickup_zonemd_verify(struct auth_zones* az, struct module_env* env); +/** Get memory usage for auth zones. The routine locks and unlocks + * for reading. */ +size_t auth_zones_get_mem(struct auth_zones* zones); + +/** + * Initial pick up of the auth zone nextprobe timeout and that turns + * into further zone transfer work, if any. Also sets the lease time. + * @param x: xfer structure, locked by caller. + * @param env: environment of the worker that picks up the task. + */ +void auth_xfer_pickup_initial_zone(struct auth_xfer* x, + struct module_env* env); + +/** + * Delete auth xfer structure + * @param xfr: delete this xfer and its tasks. + */ +void auth_xfer_delete(struct auth_xfer* xfr); + +/** + * Disown tasks from the xfr that belong to this worker. + * Only tasks for the worker in question, the comm point and timer + * delete functions need to run in the thread of that worker to be + * able to delete the callback from the event base. + * @param xfr: xfr structure + * @param worker: the worker for which to stop tasks. + */ +void xfr_disown_tasks(struct auth_xfer* xfr, struct worker* worker); + #endif /* SERVICES_AUTHZONE_H */ diff --git a/services/cache/infra.c b/services/cache/infra.c index 3833c1a09..9c3e4de43 100644 --- a/services/cache/infra.c +++ b/services/cache/infra.c @@ -161,7 +161,7 @@ rate_deldatafunc(void* d, void* ATTR_UNUSED(arg)) /** find or create element in domainlimit tree */ static struct domain_limit_data* domain_limit_findcreate( - struct infra_cache* infra, char* name) + struct rbtree_type* domain_limits, char* name) { uint8_t* nm; int labs; @@ -177,8 +177,8 @@ static struct domain_limit_data* domain_limit_findcreate( labs = dname_count_labels(nm); /* can we find it? */ - d = (struct domain_limit_data*)name_tree_find(&infra->domain_limits, - nm, nmlen, labs, LDNS_RR_CLASS_IN); + d = (struct domain_limit_data*)name_tree_find(domain_limits, nm, + nmlen, labs, LDNS_RR_CLASS_IN); if(d) { free(nm); return d; @@ -197,8 +197,8 @@ static struct domain_limit_data* domain_limit_findcreate( d->node.dclass = LDNS_RR_CLASS_IN; d->lim = -1; d->below = -1; - if(!name_tree_insert(&infra->domain_limits, &d->node, nm, nmlen, - labs, LDNS_RR_CLASS_IN)) { + if(!name_tree_insert(domain_limits, &d->node, nm, nmlen, labs, + LDNS_RR_CLASS_IN)) { log_err("duplicate element in domainlimit tree"); free(nm); free(d); @@ -208,19 +208,19 @@ static struct domain_limit_data* domain_limit_findcreate( } /** insert rate limit configuration into lookup tree */ -static int infra_ratelimit_cfg_insert(struct infra_cache* infra, +static int infra_ratelimit_cfg_insert(struct rbtree_type* domain_limits, struct config_file* cfg) { struct config_str2list* p; struct domain_limit_data* d; for(p = cfg->ratelimit_for_domain; p; p = p->next) { - d = domain_limit_findcreate(infra, p->str); + d = domain_limit_findcreate(domain_limits, p->str); if(!d) return 0; d->lim = atoi(p->str2); } for(p = cfg->ratelimit_below_domain; p; p = p->next) { - d = domain_limit_findcreate(infra, p->str); + d = domain_limit_findcreate(domain_limits, p->str); if(!d) return 0; d->below = atoi(p->str2); @@ -228,24 +228,21 @@ static int infra_ratelimit_cfg_insert(struct infra_cache* infra, return 1; } -/** setup domain limits tree (0 on failure) */ -static int -setup_domain_limits(struct infra_cache* infra, struct config_file* cfg) +int +setup_domain_limits(struct rbtree_type* domain_limits, struct config_file* cfg) { - name_tree_init(&infra->domain_limits); - if(!infra_ratelimit_cfg_insert(infra, cfg)) { + name_tree_init(domain_limits); + if(!infra_ratelimit_cfg_insert(domain_limits, cfg)) { return 0; } - name_tree_init_parents(&infra->domain_limits); + name_tree_init_parents(domain_limits); return 1; } /** find or create element in wait limit netblock tree */ static struct wait_limit_netblock_info* -wait_limit_netblock_findcreate(struct infra_cache* infra, char* str, - int cookie) +wait_limit_netblock_findcreate(struct rbtree_type* tree, char* str) { - rbtree_type* tree; struct sockaddr_storage addr; int net; socklen_t addrlen; @@ -257,10 +254,6 @@ wait_limit_netblock_findcreate(struct infra_cache* infra, char* str, } /* can we find it? */ - if(cookie) - tree = &infra->wait_limits_cookie_netblock; - else - tree = &infra->wait_limits_netblock; d = (struct wait_limit_netblock_info*)addr_tree_find(tree, &addr, addrlen, net); if(d) @@ -282,19 +275,21 @@ wait_limit_netblock_findcreate(struct infra_cache* infra, char* str, /** insert wait limit information into lookup tree */ static int -infra_wait_limit_netblock_insert(struct infra_cache* infra, - struct config_file* cfg) +infra_wait_limit_netblock_insert(rbtree_type* wait_limits_netblock, + rbtree_type* wait_limits_cookie_netblock, struct config_file* cfg) { struct config_str2list* p; struct wait_limit_netblock_info* d; for(p = cfg->wait_limit_netblock; p; p = p->next) { - d = wait_limit_netblock_findcreate(infra, p->str, 0); + d = wait_limit_netblock_findcreate(wait_limits_netblock, + p->str); if(!d) return 0; d->limit = atoi(p->str2); } for(p = cfg->wait_limit_cookie_netblock; p; p = p->next) { - d = wait_limit_netblock_findcreate(infra, p->str, 1); + d = wait_limit_netblock_findcreate(wait_limits_cookie_netblock, + p->str); if(!d) return 0; d->limit = atoi(p->str2); @@ -302,16 +297,17 @@ infra_wait_limit_netblock_insert(struct infra_cache* infra, return 1; } -/** setup wait limits tree (0 on failure) */ -static int -setup_wait_limits(struct infra_cache* infra, struct config_file* cfg) +int +setup_wait_limits(rbtree_type* wait_limits_netblock, + rbtree_type* wait_limits_cookie_netblock, struct config_file* cfg) { - addr_tree_init(&infra->wait_limits_netblock); - addr_tree_init(&infra->wait_limits_cookie_netblock); - if(!infra_wait_limit_netblock_insert(infra, cfg)) + addr_tree_init(wait_limits_netblock); + addr_tree_init(wait_limits_cookie_netblock); + if(!infra_wait_limit_netblock_insert(wait_limits_netblock, + wait_limits_cookie_netblock, cfg)) return 0; - addr_tree_init_parents(&infra->wait_limits_netblock); - addr_tree_init_parents(&infra->wait_limits_cookie_netblock); + addr_tree_init_parents(wait_limits_netblock); + addr_tree_init_parents(wait_limits_cookie_netblock); return 1; } @@ -344,11 +340,12 @@ infra_create(struct config_file* cfg) return NULL; } /* insert config data into ratelimits */ - if(!setup_domain_limits(infra, cfg)) { + if(!setup_domain_limits(&infra->domain_limits, cfg)) { infra_delete(infra); return NULL; } - if(!setup_wait_limits(infra, cfg)) { + if(!setup_wait_limits(&infra->wait_limits_netblock, + &infra->wait_limits_cookie_netblock, cfg)) { infra_delete(infra); return NULL; } @@ -373,12 +370,29 @@ static void domain_limit_free(rbnode_type* n, void* ATTR_UNUSED(arg)) } } +void +domain_limits_free(struct rbtree_type* domain_limits) +{ + if(!domain_limits) + return; + traverse_postorder(domain_limits, domain_limit_free, NULL); +} + /** delete wait_limit_netblock_info entries */ static void wait_limit_netblock_del(rbnode_type* n, void* ATTR_UNUSED(arg)) { free(n); } +void +wait_limits_free(struct rbtree_type* wait_limits_tree) +{ + if(!wait_limits_tree) + return; + traverse_postorder(wait_limits_tree, wait_limit_netblock_del, + NULL); +} + void infra_delete(struct infra_cache* infra) { @@ -386,12 +400,10 @@ infra_delete(struct infra_cache* infra) return; slabhash_delete(infra->hosts); slabhash_delete(infra->domain_rates); - traverse_postorder(&infra->domain_limits, domain_limit_free, NULL); + domain_limits_free(&infra->domain_limits); slabhash_delete(infra->client_ip_rates); - traverse_postorder(&infra->wait_limits_netblock, - wait_limit_netblock_del, NULL); - traverse_postorder(&infra->wait_limits_cookie_netblock, - wait_limit_netblock_del, NULL); + wait_limits_free(&infra->wait_limits_netblock); + wait_limits_free(&infra->wait_limits_cookie_netblock); free(infra); } @@ -422,7 +434,7 @@ infra_adjust(struct infra_cache* infra, struct config_file* cfg) /* reapply domain limits */ traverse_postorder(&infra->domain_limits, domain_limit_free, NULL); - if(!setup_domain_limits(infra, cfg)) { + if(!setup_domain_limits(&infra->domain_limits, cfg)) { infra_delete(infra); return NULL; } diff --git a/services/cache/infra.h b/services/cache/infra.h index 752a141a8..903048cb2 100644 --- a/services/cache/infra.h +++ b/services/cache/infra.h @@ -515,6 +515,21 @@ void infra_wait_limit_inc(struct infra_cache* infra, struct comm_reply* rep, void infra_wait_limit_dec(struct infra_cache* infra, struct comm_reply* rep, struct config_file* cfg); +/** setup wait limits tree (0 on failure) */ +int setup_wait_limits(struct rbtree_type* wait_limits_netblock, + struct rbtree_type* wait_limits_cookie_netblock, + struct config_file* cfg); + +/** Free the wait limits and wait cookie limits tree. */ +void wait_limits_free(struct rbtree_type* wait_limits_tree); + +/** setup domain limits tree (0 on failure) */ +int setup_domain_limits(struct rbtree_type* domain_limits, + struct config_file* cfg); + +/** Free the domain limits tree. */ +void domain_limits_free(struct rbtree_type* domain_limits); + /** exported for unit test */ int still_useful_timeout(); diff --git a/services/localzone.c b/services/localzone.c index 4ff30fd26..9ea98c250 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -2220,3 +2220,35 @@ void local_zones_del_data(struct local_zones* zones, lock_rw_unlock(&z->lock); } + +/** Get memory usage for local_zone */ +static size_t +local_zone_get_mem(struct local_zone* z) +{ + size_t m = sizeof(*z); + lock_rw_rdlock(&z->lock); + m += z->namelen + z->taglen + regional_get_mem(z->region); + lock_rw_unlock(&z->lock); + return m; +} + +size_t local_zones_get_mem(struct local_zones* zones) +{ + struct local_zone* z; + size_t m; + if(!zones) return 0; + m = sizeof(*zones); + lock_rw_rdlock(&zones->lock); + RBTREE_FOR(z, struct local_zone*, &zones->ztree) { + m += local_zone_get_mem(z); + } + lock_rw_unlock(&zones->lock); + return m; +} + +void local_zones_swap_tree(struct local_zones* zones, struct local_zones* data) +{ + rbtree_type oldtree = zones->ztree; + zones->ztree = data->ztree; + data->ztree = oldtree; +} diff --git a/services/localzone.h b/services/localzone.h index 6f0f28b12..66102fd98 100644 --- a/services/localzone.h +++ b/services/localzone.h @@ -642,6 +642,20 @@ local_zone_enter_rr(struct local_zone* z, uint8_t* nm, size_t nmlen, struct local_data* local_zone_find_data(struct local_zone* z, uint8_t* nm, size_t nmlen, int nmlabs); +/** Get memory usage for local_zones tree. The routine locks and unlocks + * the tree for reading. */ +size_t local_zones_get_mem(struct local_zones* zones); + +/** + * Swap internal tree with preallocated entries. Caller should manage + * the locks. + * @param zones: the local zones structure. + * @param data: the data structure used to take elements from. This contains + * the old elements on return. + */ +void local_zones_swap_tree(struct local_zones* zones, + struct local_zones* data); + /** Enter a new zone; returns with WRlock * Made public for unit testing * @param zones: the local zones tree diff --git a/services/mesh.c b/services/mesh.c index 2289277ea..e41f1c92b 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -77,6 +77,20 @@ #include #endif +/** Compare two views by name */ +static int +view_name_compare(const char* v_a, const char* v_b) +{ + if(v_a == NULL && v_b == NULL) + return 0; + /* The NULL name is smaller than if the name is set. */ + if(v_a == NULL) + return -1; + if(v_b == NULL) + return 1; + return strcmp(v_a, v_b); +} + /** * Compare two response-ip client info entries for the purpose of mesh state * compare. It returns 0 if ci_a and ci_b are considered equal; otherwise @@ -132,12 +146,14 @@ client_info_compare(const struct respip_client_info* ci_a, } if(ci_a->tag_datas != ci_b->tag_datas) return ci_a->tag_datas < ci_b->tag_datas ? -1 : 1; - if(ci_a->view != ci_b->view) - return ci_a->view < ci_b->view ? -1 : 1; - /* For the unbound daemon these should be non-NULL and identical, - * but we check that just in case. */ - if(ci_a->respip_set != ci_b->respip_set) - return ci_a->respip_set < ci_b->respip_set ? -1 : 1; + if(ci_a->view || ci_a->view_name || ci_b->view || ci_b->view_name) { + /* Compare the views by name. */ + cmp = view_name_compare( + (ci_a->view?ci_a->view->name:ci_a->view_name), + (ci_b->view?ci_b->view->name:ci_b->view_name)); + if(cmp != 0) + return cmp; + } return 0; } @@ -870,6 +886,72 @@ void mesh_report_reply(struct mesh_area* mesh, struct outbound_entry* e, mesh_run(mesh, e->qstate->mesh_info, event, e); } +/** copy strlist to region */ +static struct config_strlist* +cfg_region_strlist_copy(struct regional* region, struct config_strlist* list) +{ + struct config_strlist* result = NULL, *last = NULL, *s = list; + while(s) { + struct config_strlist* n = regional_alloc_zero(region, + sizeof(*n)); + if(!n) + return NULL; + n->str = regional_strdup(region, s->str); + if(!n->str) + return NULL; + if(last) + last->next = n; + else result = n; + last = n; + s = s->next; + } + return result; +} + +/** Copy the client info to the query region. */ +static struct respip_client_info* +mesh_copy_client_info(struct regional* region, struct respip_client_info* cinfo) +{ + size_t i; + struct respip_client_info* client_info; + client_info = regional_alloc_init(region, cinfo, sizeof(*cinfo)); + if(!client_info) + return NULL; + /* Copy the client_info so that if the configuration changes, + * then the data stays valid. */ + client_info->taglist = regional_alloc_init(region, cinfo->taglist, + cinfo->taglen); + if(!client_info->taglist) + return NULL; + client_info->tag_actions = regional_alloc_init(region, cinfo->tag_actions, + cinfo->tag_actions_size); + if(!client_info->tag_actions) + return NULL; + client_info->tag_datas = regional_alloc_zero(region, + sizeof(struct config_strlist*)*cinfo->tag_datas_size); + if(!client_info->tag_datas) + return NULL; + for(i=0; itag_datas_size; i++) { + if(cinfo->tag_datas[i]) { + client_info->tag_datas[i] = cfg_region_strlist_copy( + region, cinfo->tag_datas[i]); + if(!client_info->tag_datas[i]) + return NULL; + } + } + if(cinfo->view) { + /* Do not copy the view pointer but store a name instead. + * The name is looked up later when done, this means that + * the view tree can be changed, by reloads. */ + client_info->view = NULL; + client_info->view_name = regional_strdup(region, + cinfo->view->name); + if(!client_info->view_name) + return NULL; + } + return client_info; +} + struct mesh_state* mesh_state_create(struct module_env* env, struct query_info* qinfo, struct respip_client_info* cinfo, uint16_t qflags, int prime, @@ -910,8 +992,7 @@ mesh_state_create(struct module_env* env, struct query_info* qinfo, return NULL; } if(cinfo) { - mstate->s.client_info = regional_alloc_init(region, cinfo, - sizeof(*cinfo)); + mstate->s.client_info = mesh_copy_client_info(region, cinfo); if(!mstate->s.client_info) { alloc_reg_release(env->alloc, region); return NULL; @@ -1686,6 +1767,25 @@ struct mesh_state* mesh_area_find(struct mesh_area* mesh, return result; } +/** remove mesh state callback */ +int mesh_state_del_cb(struct mesh_state* s, mesh_cb_func_type cb, void* cb_arg) +{ + struct mesh_cb* r, *prev = NULL; + r = s->cb_list; + while(r) { + if(r->cb == cb && r->cb_arg == cb_arg) { + /* Delete this entry. */ + /* It was allocated in the s.region, so no free. */ + if(prev) prev->next = r->next; + else s->cb_list = r->next; + return 1; + } + prev = r; + r = r->next; + } + return 0; +} + int mesh_state_add_cb(struct mesh_state* s, struct edns_data* edns, sldns_buffer* buf, mesh_cb_func_type cb, void* cb_arg, uint16_t qid, uint16_t qflags) @@ -2151,7 +2251,8 @@ apply_respip_action(struct module_qstate* qstate, return 1; if(!respip_rewrite_reply(qinfo, cinfo, rep, encode_repp, actinfo, - alias_rrset, 0, qstate->region, az, NULL)) + alias_rrset, 0, qstate->region, az, NULL, qstate->env->views, + qstate->env->respip_set)) return 0; /* xxx_deny actions mean dropping the reply, unless the original reply @@ -2226,7 +2327,8 @@ mesh_serve_expired_callback(void* arg) } else if(partial_rep && !respip_merge_cname(partial_rep, &qstate->qinfo, msg->rep, qstate->client_info, must_validate, &encode_rep, qstate->region, - qstate->env->auth_zones)) { + qstate->env->auth_zones, qstate->env->views, + qstate->env->respip_set)) { return; } if(!encode_rep || alias_rrset) { @@ -2380,3 +2482,25 @@ int mesh_jostle_exceeded(struct mesh_area* mesh) return 0; return 1; } + +void mesh_remove_callback(struct mesh_area* mesh, struct query_info* qinfo, + uint16_t qflags, mesh_cb_func_type cb, void* cb_arg) +{ + struct mesh_state* s = NULL; + s = mesh_area_find(mesh, NULL, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); + if(!s) return; + if(!mesh_state_del_cb(s, cb, cb_arg)) return; + + /* It was in the list and removed. */ + log_assert(mesh->num_reply_addrs > 0); + mesh->num_reply_addrs--; + if(!s->reply_list && !s->cb_list) { + /* was a reply state, not anymore */ + log_assert(mesh->num_reply_states > 0); + mesh->num_reply_states--; + } + if(!s->reply_list && !s->cb_list && + s->super_set.count == 0) { + mesh->num_detached_states++; + } +} diff --git a/services/mesh.h b/services/mesh.h index 0906ed9cf..0b01d4ef8 100644 --- a/services/mesh.h +++ b/services/mesh.h @@ -706,4 +706,17 @@ int mesh_jostle_exceeded(struct mesh_area* mesh); */ void mesh_respond_serve_expired(struct mesh_state* mstate); +/** + * Remove callback from mesh. Removes the callback from the state. + * The state itself is left to run. Searches for the pointer values. + * + * @param mesh: the mesh. + * @param qinfo: query from client. + * @param qflags: flags from client query. + * @param cb: callback function. + * @param cb_arg: callback user arg. + */ +void mesh_remove_callback(struct mesh_area* mesh, struct query_info* qinfo, + uint16_t qflags, mesh_cb_func_type cb, void* cb_arg); + #endif /* SERVICES_MESH_H */ diff --git a/services/rpz.c b/services/rpz.c index 6658e89b7..df39e75b0 100644 --- a/services/rpz.c +++ b/services/rpz.c @@ -2792,3 +2792,31 @@ void rpz_disable(struct rpz* r) return; r->disabled = 1; } + +/** Get memory usage for clientip_synthesized_rrset. Ignores memory usage + * of locks. */ +static size_t +rpz_clientip_synthesized_set_get_mem(struct clientip_synthesized_rrset* set) +{ + size_t m = sizeof(*set); + lock_rw_rdlock(&set->lock); + m += regional_get_mem(set->region); + lock_rw_unlock(&set->lock); + return m; +} + +size_t rpz_get_mem(struct rpz* r) +{ + size_t m = sizeof(*r); + if(r->taglist) + m += r->taglistlen; + if(r->log_name) + m += strlen(r->log_name) + 1; + m += regional_get_mem(r->region); + m += local_zones_get_mem(r->local_zones); + m += local_zones_get_mem(r->nsdname_zones); + m += respip_set_get_mem(r->respip_set); + m += rpz_clientip_synthesized_set_get_mem(r->client_set); + m += rpz_clientip_synthesized_set_get_mem(r->ns_set); + return m; +} diff --git a/services/rpz.h b/services/rpz.h index 7f409087f..6b5f17d1e 100644 --- a/services/rpz.h +++ b/services/rpz.h @@ -269,4 +269,11 @@ void rpz_enable(struct rpz* r); */ void rpz_disable(struct rpz* r); +/** + * Get memory usage of rpz. Caller must manage locks. + * @param r: RPZ struct. + * @return memory usage. + */ +size_t rpz_get_mem(struct rpz* r); + #endif /* SERVICES_RPZ_H */ diff --git a/services/view.c b/services/view.c index 72f364318..44b07f3ed 100644 --- a/services/view.c +++ b/services/view.c @@ -43,6 +43,7 @@ #include "services/view.h" #include "services/localzone.h" #include "util/config_file.h" +#include "respip/respip.h" int view_cmp(const void* v1, const void* v2) @@ -66,11 +67,6 @@ views_create(void) return v; } -/* \noop (ignore this comment for doxygen) - * This prototype is defined in in respip.h, but we want to avoid - * unnecessary dependencies */ -void respip_set_delete(struct respip_set *set); - void view_delete(struct view* v) { @@ -247,3 +243,38 @@ void views_print(struct views* v) /* TODO implement print */ (void)v; } + +size_t views_get_mem(struct views* vs) +{ + struct view* v; + size_t m; + if(!vs) return 0; + m = sizeof(struct views); + lock_rw_rdlock(&vs->lock); + RBTREE_FOR(v, struct view*, &vs->vtree) { + m += view_get_mem(v); + } + lock_rw_unlock(&vs->lock); + return m; +} + +size_t view_get_mem(struct view* v) +{ + size_t m = sizeof(*v); + lock_rw_rdlock(&v->lock); + m += getmem_str(v->name); + m += local_zones_get_mem(v->local_zones); + m += respip_set_get_mem(v->respip_set); + lock_rw_unlock(&v->lock); + return m; +} + +void views_swap_tree(struct views* vs, struct views* data) +{ + rbnode_type* oldroot = vs->vtree.root; + size_t oldcount = vs->vtree.count; + vs->vtree.root = data->vtree.root; + vs->vtree.count = data->vtree.count; + data->vtree.root = oldroot; + data->vtree.count = oldcount; +} diff --git a/services/view.h b/services/view.h index 12f7a64e7..0ff39ed6e 100644 --- a/services/view.h +++ b/services/view.h @@ -54,7 +54,8 @@ struct respip_set; * Views storage, shared. */ struct views { - /** lock on the view tree */ + /** lock on the view tree. When locking order, the views lock + * is before the forwards,hints,anchors,localzones lock. */ lock_rw_type lock; /** rbtree of struct view */ rbtree_type vtree; @@ -135,4 +136,27 @@ void views_print(struct views* v); */ struct view* views_find_view(struct views* vs, const char* name, int write); +/** + * Calculate memory usage of views. + * @param vs: the views tree. The routine locks and unlocks the structure + * for reading. + * @return memory in bytes. + */ +size_t views_get_mem(struct views* vs); + +/** + * Calculate memory usage of view. + * @param v: the view. The routine locks and unlocks the structure for reading. + * @return memory in bytes. + */ +size_t view_get_mem(struct view* v); + +/** + * Swap internal tree with preallocated entries. Caller should manage + * the locks. + * @param vs: views tree + * @param data: preallocated information. + */ +void views_swap_tree(struct views* vs, struct views* data); + #endif /* SERVICES_VIEW_H */ diff --git a/smallapp/unbound-control.c b/smallapp/unbound-control.c index f274b9dbf..dcbe66030 100644 --- a/smallapp/unbound-control.c +++ b/smallapp/unbound-control.c @@ -109,6 +109,16 @@ usage(void) printf(" That means the caches sizes and\n"); printf(" the number of threads must not\n"); printf(" change between reloads.\n"); + printf(" fast_reload [+dpv] reloads the server but only briefly stops\n"); + printf(" server processing, keeps cache, and changes\n"); + printf(" most options; check unbound-control(8).\n"); + printf(" +d drops running queries to keep consistency\n"); + printf(" on changed options while reloading.\n"); + printf(" +p does not pause threads for even faster\n"); + printf(" reload but less options are supported\n"); + printf(" ; check unbound-control(8).\n"); + printf(" +v verbose output, it will include duration needed.\n"); + printf(" +vv more verbose output, it will include memory needed.\n"); printf(" stats print statistics\n"); printf(" stats_noreset peek at statistics\n"); #ifdef HAVE_SHMGET diff --git a/smallapp/worker_cb.c b/smallapp/worker_cb.c index 1d71a0945..92ebe386d 100644 --- a/smallapp/worker_cb.c +++ b/smallapp/worker_cb.c @@ -256,6 +256,20 @@ void dtio_mainfdcallback(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), } #endif +void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), + void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c), + void* ATTR_UNUSED(arg), int ATTR_UNUSED(error), + struct comm_reply* ATTR_UNUSED(repinfo)) +{ + log_assert(0); + return 0; +} + #ifdef HAVE_NGTCP2 void doq_client_event_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), void* ATTR_UNUSED(arg)) diff --git a/testcode/checklocks.c b/testcode/checklocks.c index fdc1b8af1..93bbf70f4 100644 --- a/testcode/checklocks.c +++ b/testcode/checklocks.c @@ -64,6 +64,9 @@ static int key_deleted = 0; static ub_thread_key_type thr_debug_key; /** the list of threads, so all threads can be examined. NULL if unused. */ static struct thr_check* thread_infos[THRDEBUG_MAX_THREADS]; +/** stored maximum lock number for threads, when a thread is restarted the + * number is kept track of, because the new locks get new id numbers. */ +static int thread_lockcount[THRDEBUG_MAX_THREADS]; /** do we check locking order */ int check_locking_order = 1; /** the pid of this runset, reasonably unique. */ @@ -698,10 +701,20 @@ open_lockorder(struct thr_check* thr) char buf[24]; time_t t; snprintf(buf, sizeof(buf), "%s.%d", output_name, thr->num); - thr->order_info = fopen(buf, "w"); - if(!thr->order_info) - fatal_exit("could not open %s: %s", buf, strerror(errno)); - thr->locks_created = 0; + thr->locks_created = thread_lockcount[thr->num]; + if(thr->locks_created == 0) { + thr->order_info = fopen(buf, "w"); + if(!thr->order_info) + fatal_exit("could not open %s: %s", buf, strerror(errno)); + } else { + /* There is already a file to append on with the previous + * thread information. */ + thr->order_info = fopen(buf, "a"); + if(!thr->order_info) + fatal_exit("could not open for append %s: %s", buf, strerror(errno)); + return; + } + t = time(NULL); /* write: */ if(fwrite(&t, sizeof(t), 1, thr->order_info) != 1 || @@ -728,6 +741,7 @@ static void* checklock_main(void* arg) if(check_locking_order) open_lockorder(thr); ret = thr->func(thr->arg); + thread_lockcount[thr->num] = thr->locks_created; thread_infos[thr->num] = NULL; if(check_locking_order) fclose(thr->order_info); diff --git a/testcode/doqclient.c b/testcode/doqclient.c index 1a2fd4183..e6f63a761 100644 --- a/testcode/doqclient.c +++ b/testcode/doqclient.c @@ -2699,3 +2699,17 @@ void dtio_mainfdcallback(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), log_assert(0); } #endif + +void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(ev), + void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c), + void* ATTR_UNUSED(arg), int ATTR_UNUSED(error), + struct comm_reply* ATTR_UNUSED(repinfo)) +{ + log_assert(0); + return 0; +} diff --git a/testcode/fake_event.c b/testcode/fake_event.c index 48843f1db..f7f321079 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -1484,6 +1484,11 @@ size_t comm_point_get_mem(struct comm_point* ATTR_UNUSED(c)) return 0; } +size_t comm_timer_get_mem(struct comm_timer* ATTR_UNUSED(timer)) +{ + return 0; +} + size_t serviced_get_mem(struct serviced_query* ATTR_UNUSED(c)) { return 0; @@ -1994,4 +1999,24 @@ void http2_stream_remove_mesh_state(struct http2_stream* ATTR_UNUSED(h2_stream)) { } +void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(event), + void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + +void fast_reload_thread_stop( + struct fast_reload_thread* ATTR_UNUSED(fast_reload_thread)) +{ + /* nothing */ +} + +int fast_reload_client_callback(struct comm_point* ATTR_UNUSED(c), + void* ATTR_UNUSED(arg), int ATTR_UNUSED(error), + struct comm_reply* ATTR_UNUSED(repinfo)) +{ + log_assert(0); + return 0; +} + /*********** End of Dummy routines ***********/ diff --git a/testcode/testbound.c b/testcode/testbound.c index f9ea86e69..6da4ceaf2 100644 --- a/testcode/testbound.c +++ b/testcode/testbound.c @@ -601,6 +601,17 @@ void listen_desetup_locks(void) /* nothing */ } +void fast_reload_printq_list_delete( + struct fast_reload_printq* ATTR_UNUSED(list)) +{ + /* nothing */ +} + +void fast_reload_worker_pickup_changes(struct worker* ATTR_UNUSED(worker)) +{ + /* nothing */ +} + #ifdef HAVE_NGTCP2 void* quic_sslctx_create(char* ATTR_UNUSED(key), char* ATTR_UNUSED(pem), char* ATTR_UNUSED(verifypem)) diff --git a/testdata/fast_reload_fwd.tdir/auth1.zone b/testdata/fast_reload_fwd.tdir/auth1.zone new file mode 100644 index 000000000..b6b551a42 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/auth1.zone @@ -0,0 +1,2 @@ +@ SOA ns root 1 3600 300 7200 3600 +www A 1.2.3.4 diff --git a/testdata/fast_reload_fwd.tdir/auth2.zone b/testdata/fast_reload_fwd.tdir/auth2.zone new file mode 100644 index 000000000..fc59810c9 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/auth2.zone @@ -0,0 +1,2 @@ +@ SOA ns root 1 3600 300 7200 3600 +www A 1.2.3.5 diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf new file mode 100644 index 000000000..dca76342f --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf @@ -0,0 +1,107 @@ +server: + verbosity: 4 + num-threads: 1 + interface: 127.0.0.1 + port: @PORT@ + use-syslog: no + directory: "" + pidfile: "unbound.pid" + chroot: "" + username: "" + do-not-query-localhost: no + trust-anchor: "ta1.example.com DS 55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af" + trust-anchor: "ta2.example.com DS 55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af" + trust-anchor: "ta3.example.com DS 55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af" + domain-insecure: "insec1.ta1.example.com" + domain-insecure: "insec2.ta1.example.com" + domain-insecure: "insec3.ta1.example.com" + +forward-zone: + name: "." + forward-addr: "127.0.0.1@12345" + +remote-control: + control-enable: yes + control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@ + control-use-cert: no + +forward-zone: + name: "example1.org" + forward-addr: "127.0.0.1@@NS1_PORT@" + +forward-zone: + name: "example2.org" + forward-addr: "127.0.0.1@@NS1_PORT@" + +forward-zone: + name: "example3.org" + forward-addr: "127.0.0.1@@NS1_PORT@" + +forward-zone: + name: "example4.org" + forward-addr: "127.0.0.1@@NS2_PORT@" + +forward-zone: + name: "example5.org" + forward-addr: "127.0.0.1@@NS2_PORT@" + +forward-zone: + name: "example6.org" + forward-addr: "127.0.0.1@@NS2_PORT@" + +stub-zone: + name: "stub1.org" + stub-addr: "127.0.0.1@@NS1_PORT@" + stub-prime: no + +stub-zone: + name: "stub2.org" + stub-addr: "127.0.0.1@@NS1_PORT@" + stub-prime: no + +stub-zone: + name: "stub3.org" + stub-addr: "127.0.0.1@@NS1_PORT@" + stub-prime: no + +stub-zone: + name: "stub4.org" + stub-addr: "127.0.0.1@@NS2_PORT@" + stub-prime: no + +stub-zone: + name: "stub5.org" + stub-addr: "127.0.0.1@@NS2_PORT@" + stub-prime: no + +stub-zone: + name: "stub6.org" + stub-addr: "127.0.0.1@@NS2_PORT@" + stub-prime: no + +auth-zone: + name: "auth1.org" + zonefile: "auth1.zone" + +auth-zone: + name: "auth2.org" + zonefile: "auth1.zone" + +auth-zone: + name: "auth3.org" + zonefile: "auth1.zone" + +auth-zone: + name: "auth5.org" + zonefile: "auth5.zone" + primary: 127.0.0.1@@NS1_PORT@ + +auth-zone: + name: "auth6.org" + zonefile: "auth6.zone" + primary: 127.0.0.1@@NS1_PORT@ + +auth-zone: + name: "auth7.org" + zonefile: "auth7.zone" + primary: 127.0.0.1@@NS1_PORT@ diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf2 b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf2 new file mode 100644 index 000000000..dbe6e4ffa --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.conf2 @@ -0,0 +1,108 @@ +server: + verbosity: 4 + num-threads: 1 + interface: 127.0.0.1 + port: @PORT@ + use-syslog: no + directory: "" + pidfile: "unbound.pid" + chroot: "" + username: "" + do-not-query-localhost: no + trust-anchor: "ta1.example.com DS 55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af" + trust-anchor: "ta3.example.com DS 55567 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af" + trust-anchor: "ta4.example.com DS 55566 8 2 9c148338951ce1c3b5cd3da532f3d90dfcf92595148022f2c2fd98e5deee90af" + domain-insecure: "insec1.ta1.example.com" + domain-insecure: "insec3.ta1.example.com" + domain-insecure: "insec4.ta1.example.com" + +forward-zone: + name: "." + # No addresses makes the server return SERVFAIL for deleted zones. + #forward-addr: "127.0.0.1@12345" + +remote-control: + control-enable: yes + control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@ + control-use-cert: no + +forward-zone: + name: "example1.org" + forward-addr: "127.0.0.1@@NS2_PORT@" + +forward-zone: + name: "example2.org" + forward-addr: "127.0.0.1@@NS1_PORT@" + +forward-zone: + name: "example3.org" + forward-addr: "127.0.0.1@@NS2_PORT@" + +forward-zone: + name: "example4.org" + forward-addr: "127.0.0.1@@NS1_PORT@" + +forward-zone: + name: "example5.org" + forward-addr: "127.0.0.1@@NS2_PORT@" + +forward-zone: + name: "example6.org" + forward-addr: "127.0.0.1@@NS1_PORT@" + +stub-zone: + name: "stub1.org" + stub-addr: "127.0.0.1@@NS2_PORT@" + stub-prime: no + +stub-zone: + name: "stub2.org" + stub-addr: "127.0.0.1@@NS1_PORT@" + stub-prime: no + +stub-zone: + name: "stub3.org" + stub-addr: "127.0.0.1@@NS2_PORT@" + stub-prime: no + +stub-zone: + name: "stub4.org" + stub-addr: "127.0.0.1@@NS1_PORT@" + stub-prime: no + +stub-zone: + name: "stub5.org" + stub-addr: "127.0.0.1@@NS2_PORT@" + stub-prime: no + +stub-zone: + name: "stub6.org" + stub-addr: "127.0.0.1@@NS1_PORT@" + stub-prime: no + +auth-zone: + name: "auth1.org" + zonefile: "auth1.zone" + +auth-zone: + name: "auth3.org" + zonefile: "auth2.zone" + +auth-zone: + name: "auth4.org" + zonefile: "auth2.zone" + +auth-zone: + name: "auth5.org" + zonefile: "auth5.zone" + primary: 127.0.0.1@@NS1_PORT@ + +auth-zone: + name: "auth7.org" + zonefile: "auth7.zone" + primary: 127.0.0.1@@NS2_PORT@ + +auth-zone: + name: "auth8.org" + zonefile: "auth8.zone" + primary: 127.0.0.1@@NS1_PORT@ diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.dsc b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.dsc new file mode 100644 index 000000000..422cdee46 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.dsc @@ -0,0 +1,16 @@ +BaseName: fast_reload_fwd +Version: 1.0 +Description: Test fast reload change of forwards and stubs. +CreationDate: Thu Jan 22 11:55:55 CET 2024 +Maintainer: dr. W.C.A. Wijngaards +Category: +Component: +CmdDepends: +Depends: +Help: +Pre: fast_reload_fwd.pre +Post: fast_reload_fwd.post +Test: fast_reload_fwd.test +AuxFiles: +Passed: +Failure: diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns1 b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns1 new file mode 100644 index 000000000..d9644414b --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns1 @@ -0,0 +1,339 @@ +; match A records and return a reply indicating it is this server. +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example1.org. IN A +SECTION ANSWER +www.example1.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example2.org. IN A +SECTION ANSWER +www.example2.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example3.org. IN A +SECTION ANSWER +www.example3.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example4.org. IN A +SECTION ANSWER +www.example4.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example5.org. IN A +SECTION ANSWER +www.example5.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example6.org. IN A +SECTION ANSWER +www.example6.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.example1.org. IN A +SECTION ANSWER +www2.example1.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.example2.org. IN A +SECTION ANSWER +www2.example2.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.example3.org. IN A +SECTION ANSWER +www2.example3.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.example4.org. IN A +SECTION ANSWER +www2.example4.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.example5.org. IN A +SECTION ANSWER +www2.example5.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.example6.org. IN A +SECTION ANSWER +www2.example6.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.stub1.org. IN A +SECTION ANSWER +www.stub1.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.stub2.org. IN A +SECTION ANSWER +www.stub2.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.stub3.org. IN A +SECTION ANSWER +www.stub3.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.stub4.org. IN A +SECTION ANSWER +www.stub4.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.stub5.org. IN A +SECTION ANSWER +www.stub5.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.stub6.org. IN A +SECTION ANSWER +www.stub6.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.stub1.org. IN A +SECTION ANSWER +www2.stub1.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.stub2.org. IN A +SECTION ANSWER +www2.stub2.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.stub3.org. IN A +SECTION ANSWER +www2.stub3.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.stub4.org. IN A +SECTION ANSWER +www2.stub4.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.stub5.org. IN A +SECTION ANSWER +www2.stub5.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.stub6.org. IN A +SECTION ANSWER +www2.stub6.org. IN A 1.2.3.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +auth5.org. IN SOA +SECTION ANSWER +auth5.org. SOA ns root 1 3600 300 7200 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +auth5.org. IN AXFR +SECTION ANSWER +auth5.org. SOA ns root 1 3600 300 7200 3600 +www.auth5.org. A 1.2.3.4 +auth5.org. SOA ns root 1 3600 300 7200 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +auth6.org. IN SOA +SECTION ANSWER +auth6.org. SOA ns root 1 3600 300 7200 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +auth6.org. IN AXFR +SECTION ANSWER +auth6.org. SOA ns root 1 3600 300 7200 3600 +www.auth6.org. A 1.2.3.4 +auth6.org. SOA ns root 1 3600 300 7200 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +auth7.org. IN SOA +SECTION ANSWER +auth7.org. SOA ns root 1 3600 300 7200 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +auth7.org. IN AXFR +SECTION ANSWER +auth7.org. SOA ns root 1 3600 300 7200 3600 +www.auth7.org. A 1.2.3.4 +auth7.org. SOA ns root 1 3600 300 7200 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +auth8.org. IN SOA +SECTION ANSWER +auth8.org. SOA ns root 1 3600 300 7200 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +auth8.org. IN AXFR +SECTION ANSWER +auth8.org. SOA ns root 1 3600 300 7200 3600 +www.auth8.org. A 1.2.3.4 +auth8.org. SOA ns root 1 3600 300 7200 3600 +ENTRY_END + +; match anything and return a reply +ENTRY_BEGIN +MATCH opcode +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +example.org. IN SOA +SECTION AUTHORITY +example.org. IN SOA ns1.example.org. hostmaster.example.org. 1 3600 900 86400 3600 +ENTRY_END diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns2 b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns2 new file mode 100644 index 000000000..8e7eb60c8 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.ns2 @@ -0,0 +1,285 @@ +; match A records and return a reply indicating it is this server. +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example1.org. IN A +SECTION ANSWER +www.example1.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example2.org. IN A +SECTION ANSWER +www.example2.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example3.org. IN A +SECTION ANSWER +www.example3.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example4.org. IN A +SECTION ANSWER +www.example4.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example5.org. IN A +SECTION ANSWER +www.example5.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example6.org. IN A +SECTION ANSWER +www.example6.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.example1.org. IN A +SECTION ANSWER +www2.example1.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.example2.org. IN A +SECTION ANSWER +www2.example2.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.example3.org. IN A +SECTION ANSWER +www2.example3.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.example4.org. IN A +SECTION ANSWER +www2.example4.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.example5.org. IN A +SECTION ANSWER +www2.example5.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.example6.org. IN A +SECTION ANSWER +www2.example6.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.stub1.org. IN A +SECTION ANSWER +www.stub1.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.stub2.org. IN A +SECTION ANSWER +www.stub2.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.stub3.org. IN A +SECTION ANSWER +www.stub3.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.stub4.org. IN A +SECTION ANSWER +www.stub4.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.stub5.org. IN A +SECTION ANSWER +www.stub5.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.stub6.org. IN A +SECTION ANSWER +www.stub6.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.stub1.org. IN A +SECTION ANSWER +www2.stub1.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.stub2.org. IN A +SECTION ANSWER +www2.stub2.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.stub3.org. IN A +SECTION ANSWER +www2.stub3.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.stub4.org. IN A +SECTION ANSWER +www2.stub4.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.stub5.org. IN A +SECTION ANSWER +www2.stub5.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.stub6.org. IN A +SECTION ANSWER +www2.stub6.org. IN A 1.2.3.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +auth7.org. IN SOA +SECTION ANSWER +auth7.org. SOA ns root 2 3600 300 7200 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +auth7.org. IN AXFR +SECTION ANSWER +auth7.org. SOA ns root 2 3600 300 7200 3600 +www.auth7.org. A 1.2.3.5 +auth7.org. SOA ns root 2 3600 300 7200 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +auth7.org. IN IXFR +SECTION ANSWER +auth7.org. SOA ns root 2 3600 300 7200 3600 +www.auth7.org. A 1.2.3.5 +auth7.org. SOA ns root 2 3600 300 7200 3600 +ENTRY_END + +; match anything and return a reply +ENTRY_BEGIN +MATCH opcode +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +example.org. IN SOA +SECTION AUTHORITY +example.org. IN SOA ns1.example.org. hostmaster.example.org. 1 3600 900 86400 3600 +ENTRY_END diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.post b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.post new file mode 100644 index 000000000..e7e644b7a --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.post @@ -0,0 +1,25 @@ +# #-- fast_reload_fwd.post --# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# source the test var file when it's there +[ -f .tpkg.var.test ] && source .tpkg.var.test +# +# do your teardown here +PRE="../.." +. ../common.sh +kill_pid $NS1_PID +kill_pid $NS2_PID +if test -f unbound.pid; then + kill_pid $UNBOUND_PID +fi +rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID +echo +echo "> ns1.log" +cat ns1.log +echo +echo "> ns2.log" +cat ns2.log +echo +echo "> unbound.log" +cat unbound.log +exit 0 diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.pre b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.pre new file mode 100644 index 000000000..42e680d8f --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.pre @@ -0,0 +1,56 @@ +# #-- fast_reload_fwd.pre--# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# use .tpkg.var.test for in test variable passing +[ -f .tpkg.var.test ] && source .tpkg.var.test + +PRE="../.." +. ../common.sh +# if no threads; exit +if grep -e "define HAVE_PTHREAD 1" -e "define HAVE_SOLARIS_THREADS 1" -e "define HAVE_WINDOWS_THREADS 1" $PRE/config.h; then + echo "have threads" +else + skip_test "no threads" +fi +if grep -e "define ENABLE_LOCK_CHECKS 1" $PRE/config.h; then + get_make + echo "> (cd $PRE ; $MAKE lock-verify)" + (cd $PRE ; $MAKE lock-verify) +fi + +get_random_port 3 +UNBOUND_PORT=$RND_PORT +NS1_PORT=$(($RND_PORT + 1)) +NS2_PORT=$(($RND_PORT + 2)) +echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test +echo "NS1_PORT=$NS1_PORT" >> .tpkg.var.test +echo "NS2_PORT=$NS2_PORT" >> .tpkg.var.test + +# make config files +CONTROL_PATH=/tmp +CONTROL_PID=$$ +sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@NS1_PORT\@/'$NS1_PORT'/' -e 's/@NS2_PORT\@/'$NS2_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < fast_reload_fwd.conf > ub.conf +sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@NS1_PORT\@/'$NS1_PORT'/' -e 's/@NS2_PORT\@/'$NS2_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < fast_reload_fwd.conf2 > ub.conf2 + +# start forwarders +get_ldns_testns +$LDNS_TESTNS -p $NS1_PORT fast_reload_fwd.ns1 >ns1.log 2>&1 & +NS1_PID=$! +echo "NS1_PID=$NS1_PID" >> .tpkg.var.test + +$LDNS_TESTNS -p $NS2_PORT fast_reload_fwd.ns2 >ns2.log 2>&1 & +NS2_PID=$! +echo "NS2_PID=$NS2_PID" >> .tpkg.var.test + +# start unbound in the background +PRE="../.." +$PRE/unbound -d -c ub.conf >unbound.log 2>&1 & +UNBOUND_PID=$! +echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test +echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test +echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test + +cat .tpkg.var.test +wait_ldns_testns_up ns1.log +wait_ldns_testns_up ns2.log +wait_unbound_up unbound.log diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.test b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.test new file mode 100644 index 000000000..9248593c7 --- /dev/null +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.test @@ -0,0 +1,320 @@ +# #-- fast_reload_fwd.test --# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# use .tpkg.var.test for in test variable passing +[ -f .tpkg.var.test ] && source .tpkg.var.test + +PRE="../.." +. ../common.sh + +echo "> unbound-control status" +$PRE/unbound-control -c ub.conf status +if test $? -ne 0; then + echo "wrong exit value." + exit 1 +else + echo "exit value: OK" +fi + +# test that the forwards and stubs point to the right upstream. +for x in example1.org example2.org example3.org stub1.org stub2.org stub3.org; do + echo "" + echo "dig www.$x [upstream is NS1]" + dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile + if grep "1.2.3.1" outfile; then + echo "response OK" + else + echo "www.$x got the wrong answer" + exit 1 + fi +done + +for x in example4.org example5.org example6.org stub4.org stub5.org stub6.org; do + echo "" + echo "dig www.$x [upstream is NS2]" + dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile + if grep "1.2.3.2" outfile; then + echo "response OK" + else + echo "www.$x got the wrong answer" + exit 1 + fi +done + +for x in auth1.org auth2.org auth3.org auth5.org auth6.org auth7.org; do + echo "" + echo "dig www.$x [auth is 1.2.3.4]" + dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile + if grep "1.2.3.4" outfile; then + echo "response OK" + else + echo "www.$x got the wrong answer" + exit 1 + fi +done + +echo "" +echo "> list_insecure" +$PRE/unbound-control -c ub.conf list_insecure 2>&1 | tee output +if test $? -ne 0; then + echo "wrong exit value." + exit 1 +fi +if grep "insec1.ta1.example.com" output >/dev/null; then :; else + echo "wrong output" + exit 1 +fi +if grep "insec2.ta1.example.com" output >/dev/null; then :; else + echo "wrong output" + exit 1 +fi +if grep "insec3.ta1.example.com" output >/dev/null; then :; else + echo "wrong output" + exit 1 +fi +echo "" +echo "> trustanchor.unbound" +dig @127.0.0.1 -p $UNBOUND_PORT trustanchor.unbound CH TXT 2>&1 | tee outfile +if grep "ta1.example.com. 55566" outfile >/dev/null; then :; else + echo "wrong output ta1" + exit 1 +fi +if grep "ta2.example.com. 55566" outfile >/dev/null; then :; else + echo "wrong output" + exit 1 +fi +if grep "ta3.example.com. 55566" outfile >/dev/null; then :; else + echo "wrong output" + exit 1 +fi + +echo "" +echo "> replace config file ub.conf" +mv ub.conf ub.conf.orig +mv ub.conf2 ub.conf +echo "" +echo "> unbound-control fast_reload" +$PRE/unbound-control -c ub.conf fast_reload +vv 2>&1 | tee output +if test $? -ne 0; then + echo "wrong exit value." + exit 1 +else + echo "exit value: OK" +fi + +# for the previous digs to www.x the cached value should remain the same +# but for new lookups, to www2.x the new upstream should be used. +for x in example1.org example2.org example3.org stub1.org stub2.org stub3.org; do + echo "" + echo "dig www.$x [upstream is NS1]" + dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile + if grep "1.2.3.1" outfile; then + echo "response OK" + else + echo "www.$x got the wrong answer" + exit 1 + fi +done + +for x in example4.org example5.org example6.org stub4.org stub5.org stub6.org; do + echo "" + echo "dig www.$x [upstream is NS2]" + dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile + if grep "1.2.3.2" outfile; then + echo "response OK" + else + echo "www.$x got the wrong answer" + exit 1 + fi +done + +# new lookups for www2 go to the upstream. +for x in example2.org example4.org example6.org stub2.org stub4.org stub6.org; do + echo "" + echo "dig www2.$x [upstream is NS1]" + dig @127.0.0.1 -p $UNBOUND_PORT www2.$x A 2>&1 | tee outfile + if grep "1.2.3.1" outfile; then + echo "response OK" + else + echo "www2.$x got the wrong answer" + exit 1 + fi +done + +for x in example1.org example3.org example5.org stub1.org stub3.org stub5.org; do + echo "" + echo "dig www2.$x [upstream is NS2]" + dig @127.0.0.1 -p $UNBOUND_PORT www2.$x A 2>&1 | tee outfile + if grep "1.2.3.2" outfile; then + echo "response OK" + else + echo "www2.$x got the wrong answer" + exit 1 + fi +done + +# auth is unchanged, or at ns1. +for x in auth1.org auth5.org auth8.org; do + echo "" + echo "dig www.$x [auth is 1.2.3.4]" + dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile + if grep "1.2.3.4" outfile; then + echo "response OK" + else + echo "www.$x got the wrong answer" + exit 1 + fi +done + +# deleted auth +for x in auth2.org auth6.org; do + echo "" + echo "dig www.$x [auth is deleted]" + dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile + if grep "SERVFAIL" outfile; then + echo "response OK" + else + echo "www.$x got the wrong answer" + exit 1 + fi +done + +# changed and added auth +for x in auth3.org auth4.org auth7.org; do + echo "" + echo "dig www.$x [auth is 1.2.3.5]" + dig @127.0.0.1 -p $UNBOUND_PORT www.$x A 2>&1 | tee outfile + if grep "1.2.3.5" outfile; then + echo "response OK" + else + echo "www.$x got the wrong answer" + exit 1 + fi +done + +echo "" +echo "> list_insecure" +$PRE/unbound-control -c ub.conf list_insecure 2>&1 | tee output +if test $? -ne 0; then + echo "wrong exit value." + exit 1 +fi +if grep "insec1.ta1.example.com" output >/dev/null; then :; else + echo "wrong output" + exit 1 +fi +if grep "insec2.ta1.example.com" output >/dev/null; then + echo "wrong output" + exit 1 +fi +if grep "insec3.ta1.example.com" output >/dev/null; then :; else + echo "wrong output" + exit 1 +fi +if grep "insec4.ta1.example.com" output >/dev/null; then :; else + echo "wrong output" + exit 1 +fi +echo "" +echo "> trustanchor.unbound" +dig @127.0.0.1 -p $UNBOUND_PORT trustanchor.unbound CH TXT 2>&1 | tee outfile +if grep "ta1.example.com. 55566" outfile >/dev/null; then :; else + echo "wrong output" + exit 1 +fi +if grep "ta2.example.com. 55566" outfile >/dev/null; then + echo "wrong output" + exit 1 +fi +if grep "ta3.example.com. 55566" outfile >/dev/null; then + echo "wrong output" + exit 1 +fi +if grep "ta3.example.com. 55567" outfile >/dev/null; then :; else + echo "wrong output" + exit 1 +fi +if grep "ta4.example.com. 55566" outfile >/dev/null; then :; else + echo "wrong output" + exit 1 +fi + +echo "" +echo "> test change: add tag1 tag2" +cp ub.conf ub.conf.orig2 +echo "server:" >> ub.conf +echo ' define-tag: "tag1 tag2"' >> ub.conf +echo "> unbound-control fast_reload" +$PRE/unbound-control -c ub.conf fast_reload +vv 2>&1 | tee output +if test $? -ne 0; then + echo "wrong exit value." + exit 1 +else + echo "exit value: OK" +fi + +echo "" +echo "> test change: change to tag2 tag3" +cp ub.conf.orig2 ub.conf +echo "server:" >> ub.conf +echo ' define-tag: "tag2 tag3"' >> ub.conf +echo "> unbound-control fast_reload" +$PRE/unbound-control -c ub.conf fast_reload +vv 2>&1 | tee output +if test $? -ne 0; then + echo "wrong exit value." + exit 1 +else + echo "exit value: OK" +fi +if grep "tags have changed" output; then + echo "output OK" +else + echo "wrong output" + exit 1 +fi + +echo "" +echo "> test change: change cache size" +cp ub.conf.orig2 ub.conf +echo "server:" >> ub.conf +echo " msg-cache-size: 10m" >> ub.conf +echo " rrset-cache-size: 5m" >> ub.conf +echo "> unbound-control fast_reload" +$PRE/unbound-control -c ub.conf fast_reload +vv 2>&1 | tee output +if test $? -ne 0; then + echo "wrong exit value." + exit 1 +else + echo "exit value: OK" +fi + +echo "" +echo "> test change: change nothing, +p too" +$PRE/unbound-control -c ub.conf fast_reload +vv +p 2>&1 | tee output +if test $? -ne 0; then + echo "wrong exit value." + exit 1 +else + echo "exit value: OK" +fi + +echo "" +echo "> stop unbound" +kill_pid $UNBOUND_PID +if test -f unbound.pid; then sleep 1; fi +if test -f unbound.pid; then sleep 1; fi +if test -f unbound.pid; then sleep 1; fi +if test -f unbound.pid; then echo "unbound.pid still there"; fi +# check the locks. +function locktest() { + if test -x $PRE/lock-verify -a -f ublocktrace.0; then + $PRE/lock-verify ublocktrace.* + if test $? -ne 0; then + echo "lock-verify error" + exit 1 + fi + fi +} +locktest + +exit 0 diff --git a/testdata/fast_reload_most_options.tdir/auth.nlnetlabs.nl.zone b/testdata/fast_reload_most_options.tdir/auth.nlnetlabs.nl.zone new file mode 100644 index 000000000..55b8d34a9 --- /dev/null +++ b/testdata/fast_reload_most_options.tdir/auth.nlnetlabs.nl.zone @@ -0,0 +1,3 @@ +$ORIGIN auth.nlnetlabs.nl. +$TTL 60 +@ IN SOA a b 1 2 3 4 5 diff --git a/testdata/fast_reload_most_options.tdir/fast_reload_most_options.conf b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.conf new file mode 100644 index 000000000..eda3d6763 --- /dev/null +++ b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.conf @@ -0,0 +1,143 @@ +# Try to define values for options that don't have "default" options that would +# trigger fast-reload functionality. +server: + verbosity: 4 + num-threads: 4 + interface: 127.0.0.1 + interface: lo + port: @PORT@ + interface-action: lo allow + use-syslog: no + directory: "" + pidfile: "unbound.pid" + chroot: "" + username: "" + do-not-query-localhost: no + + module-config: "respip validator iterator" + + outgoing-interface: 127.0.0.1 + outgoing-port-avoid: "3200-3208" + + define-tag: "tag1 tag2 tag3" + + do-nat64: yes + nat64-prefix: 64:ff9b::0/96 + dns64-prefix: 64:ff9b::0/96 + dns64-ignore-aaaa: "ignore-aaaa.nlnetlabs.nl" + + edns-tcp-keepalive: yes + + response-ip: 192.0.2.0 always_refuse + access-control: 127.0.0.0/8 allow + access-control: ::1 allow + access-control-tag: 192.0.2.0/24 "tag2 tag3" + interface-tag: lo "tag2 tag3" + access-control-tag-action: 192.0.2.0/24 tag3 always_refuse + interface-tag-action: lo tag3 always_refuse + access-control-tag-data: 192.0.2.0/24 tag2 "A 127.0.0.1" + interface-tag-data: lo tag2 "A 127.0.0.1" + access-control-view: 192.0.2.0/24 viewname + interface-view: lo viewname + + nsid: "ascii_something" + + http-user-agent: "httpuseragent" + + caps-exempt: "nlnetlabs.nl" + + private-address: 10.0.0.0/8 + private-address: 172.16.0.0/12 + private-address: 192.168.0.0/16 + private-address: 169.254.0.0/16 + private-address: fd00::/8 + private-address: fe80::/10 + private-address: ::ffff:0:0/96 + + private-domain: "nlnetlabs.nl" + + unwanted-reply-threshold: 10000000 + + do-not-query-address: 1.1.1.1 + do-not-query-address: 8.8.8.8 + do-not-query-address: 9.9.9.9 + + do-not-query-localhost: no + + trust-anchor: "jelte.nlnetlabs.nl. DS 42860 5 1 14D739EB566D2B1A5E216A0BA4D17FA9B038BE4A" + + domain-insecure: "nlnetlabs.nl" + + serve-expired: yes + serve-expired-client-timeout: 1800 + + val-log-level: 2 + + local-zone: refuse.nlnetlabs.nl. refuse + local-zone: override.nlnetlabs.nl. deny + local-zone: tag.nlnetlabs.nl. transparent + local-data: "data.nlnetlabs.nl. TXT localdata" + local-data-ptr: "192.0.2.3 reverse.nlnetlabs.nl." + local-zone-tag: "tag.nlnetlabs.nl" "tag2 tag3" + local-zone-override: "override.nlnetlabs.nl" 192.0.2.0/24 refuse + + + ratelimit: 100 + ratelimit-below-domain: ratelimit.nlnetlabs.nl 1000 + ip-ratelimit: 100 + + tcp-connection-limit: 192.0.2.0/24 12 + + answer-cookie: yes + cookie-secret: "000102030405060708090a0b0c0d0e0f" + + ede: yes + ede-serve-expired: yes + +remote-control: + control-enable: yes + control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@ + control-use-cert: no + +stub-zone: + name: "stub.nlnetlabs.nl" + stub-addr: 192.0.2.68 + stub-prime: no + stub-first: no + stub-tcp-upstream: no + stub-tls-upstream: no + stub-no-cache: no + +forward-zone: + name: "forward.nlnetlabs.nl" + forward-addr: 192.0.2.68 + forward-first: no + forward-tcp-upstream: no + forward-tls-upstream: no + forward-no-cache: no + +auth-zone: + name: "auth.nlnetlabs.nl" + for-downstream: yes + for-upstream: yes + zonemd-check: no + zonemd-reject-absence: no + zonefile: "auth.nlnetlabs.nl.zone" + +view: + name: "viewname" + local-zone: "view.nlnetlabs.nl" redirect + local-data: "view.nlnetlabs.nl A 192.0.2.3" + local-data-ptr: "192.0.2.3 view.nlnetlabs.nl" + view-first: no + +rpz: + name: "rpz.nlnetlabs.nl" + zonefile: "rpz.nlnetlabs.nl.zone" + rpz-action-override: cname + rpz-cname-override: www.example.org + rpz-log: yes + rpz-log-name: "example policy" + rpz-signal-nxdomain-ra: no + for-downstream: no + tags: "tag3" diff --git a/testdata/fast_reload_most_options.tdir/fast_reload_most_options.dsc b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.dsc new file mode 100644 index 000000000..e0e8e9fd2 --- /dev/null +++ b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.dsc @@ -0,0 +1,16 @@ +BaseName: fast_reload_most_options +Version: 1.0 +Description: Test fast reload on high verbosity with most options. +CreationDate: Fri 28 Feb 2025 15:55:15 CET +Maintainer: Yorgos Thessalonikefs +Category: +Component: +CmdDepends: +Depends: +Help: +Pre: fast_reload_most_options.pre +Post: fast_reload_most_options.post +Test: fast_reload_most_options.test +AuxFiles: +Passed: +Failure: diff --git a/testdata/fast_reload_most_options.tdir/fast_reload_most_options.post b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.post new file mode 100644 index 000000000..7fd25e364 --- /dev/null +++ b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.post @@ -0,0 +1,11 @@ +# #-- fast_reload_most_options.post --# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# source the test var file when it's there +[ -f .tpkg.var.test ] && source .tpkg.var.test +# +# do your teardown here +. ../common.sh +kill_pid $UNBOUND_PID +rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID +cat unbound.log diff --git a/testdata/fast_reload_most_options.tdir/fast_reload_most_options.pre b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.pre new file mode 100644 index 000000000..47e3642c6 --- /dev/null +++ b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.pre @@ -0,0 +1,33 @@ +# #-- fast_reload_most_options.pre--# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# use .tpkg.var.test for in test variable passing +[ -f .tpkg.var.test ] && source .tpkg.var.test + +PRE="../.." +. ../common.sh +# if no threads; exit +if grep -e "define HAVE_PTHREAD 1" -e "define HAVE_SOLARIS_THREADS 1" -e "define HAVE_WINDOWS_THREADS 1" $PRE/config.h; then + echo "have threads" +else + skip_test "no threads" +fi + +get_random_port 1 +UNBOUND_PORT=$RND_PORT +echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test + +# make config file +CONTROL_PATH=/tmp +CONTROL_PID=$$ +sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < fast_reload_most_options.conf > ub.conf +# start unbound in the background +PRE="../.." +$PRE/unbound -d -c ub.conf >unbound.log 2>&1 & +UNBOUND_PID=$! +echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test +echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test +echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test + +cat .tpkg.var.test +wait_unbound_up unbound.log diff --git a/testdata/fast_reload_most_options.tdir/fast_reload_most_options.test b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.test new file mode 100644 index 000000000..207999860 --- /dev/null +++ b/testdata/fast_reload_most_options.tdir/fast_reload_most_options.test @@ -0,0 +1,42 @@ +# #-- fast_reload_most_options.test --# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# use .tpkg.var.test for in test variable passing +[ -f .tpkg.var.test ] && source .tpkg.var.test + +PRE="../.." +. ../common.sh + +echo "> unbound-control status" +$PRE/unbound-control -c ub.conf status +if test $? -ne 0; then + echo "wrong exit value." + exit 1 +else + echo "exit value: OK" +fi + +for i in {1..10} +do + + echo "> unbound-control fast_reload +vvdp ($i)" + $PRE/unbound-control -c ub.conf fast_reload +vvdp 2>&1 | tee output + if test $? -ne 0; then + echo "wrong exit value." + exit 1 + else + echo "exit value: OK" + fi + wait_logfile unbound.log "start fast reload thread" 60 + wait_logfile unbound.log "stop fast reload thread" 60 + wait_logfile unbound.log "joined with fastreload thread" 60 + + if grep "ok" output; then + echo "OK" + else + echo "output not correct" + exit 1 + fi +done + +exit 0 diff --git a/testdata/fast_reload_most_options.tdir/rpz.nlnetlabs.nl.zone b/testdata/fast_reload_most_options.tdir/rpz.nlnetlabs.nl.zone new file mode 100644 index 000000000..71b371506 --- /dev/null +++ b/testdata/fast_reload_most_options.tdir/rpz.nlnetlabs.nl.zone @@ -0,0 +1,5 @@ +$ORIGIN rpz.nlnetlabs.nl. +$TTL 60 +@ IN SOA a b 1 2 3 4 5 +nxdomain.nlnetlabs.nl IN CNAME . +rpzdata.nlnetlabs.nl IN A 0.0.0.0 diff --git a/testdata/fast_reload_thread.tdir/fast_reload_thread.conf b/testdata/fast_reload_thread.tdir/fast_reload_thread.conf new file mode 100644 index 000000000..719f4a00e --- /dev/null +++ b/testdata/fast_reload_thread.tdir/fast_reload_thread.conf @@ -0,0 +1,20 @@ +server: + verbosity: 4 + num-threads: 1 + interface: 127.0.0.1 + port: @PORT@ + use-syslog: no + directory: "" + pidfile: "unbound.pid" + chroot: "" + username: "" + do-not-query-localhost: no + +forward-zone: + name: "." + forward-addr: "127.0.0.1@12345" + +remote-control: + control-enable: yes + control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@ + control-use-cert: no diff --git a/testdata/fast_reload_thread.tdir/fast_reload_thread.dsc b/testdata/fast_reload_thread.tdir/fast_reload_thread.dsc new file mode 100644 index 000000000..ec3437b69 --- /dev/null +++ b/testdata/fast_reload_thread.tdir/fast_reload_thread.dsc @@ -0,0 +1,16 @@ +BaseName: fast_reload_thread +Version: 1.0 +Description: Test fast reload thread output. +CreationDate: Thu Jan 4 09:25:55 CET 2024 +Maintainer: dr. W.C.A. Wijngaards +Category: +Component: +CmdDepends: +Depends: +Help: +Pre: fast_reload_thread.pre +Post: fast_reload_thread.post +Test: fast_reload_thread.test +AuxFiles: +Passed: +Failure: diff --git a/testdata/fast_reload_thread.tdir/fast_reload_thread.post b/testdata/fast_reload_thread.tdir/fast_reload_thread.post new file mode 100644 index 000000000..569a17f85 --- /dev/null +++ b/testdata/fast_reload_thread.tdir/fast_reload_thread.post @@ -0,0 +1,11 @@ +# #-- fast_reload_thread.post --# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# source the test var file when it's there +[ -f .tpkg.var.test ] && source .tpkg.var.test +# +# do your teardown here +. ../common.sh +kill_pid $UNBOUND_PID +rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID +cat unbound.log diff --git a/testdata/fast_reload_thread.tdir/fast_reload_thread.pre b/testdata/fast_reload_thread.tdir/fast_reload_thread.pre new file mode 100644 index 000000000..5521742fa --- /dev/null +++ b/testdata/fast_reload_thread.tdir/fast_reload_thread.pre @@ -0,0 +1,34 @@ +# #-- fast_reload_thread.pre--# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# use .tpkg.var.test for in test variable passing +[ -f .tpkg.var.test ] && source .tpkg.var.test + +PRE="../.." +. ../common.sh +# if no threads; exit +if grep -e "define HAVE_PTHREAD 1" -e "define HAVE_SOLARIS_THREADS 1" -e "define HAVE_WINDOWS_THREADS 1" $PRE/config.h; then + echo "have threads" +else + skip_test "no threads" +fi + +get_random_port 1 +UNBOUND_PORT=$RND_PORT +echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test + +# make config file +CONTROL_PATH=/tmp +CONTROL_PID=$$ +sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < fast_reload_thread.conf > ub.conf +# start unbound in the background +PRE="../.." +$PRE/unbound -d -c ub.conf >unbound.log 2>&1 & +UNBOUND_PID=$! +echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test +echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test +echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test + +cat .tpkg.var.test +wait_unbound_up unbound.log + diff --git a/testdata/fast_reload_thread.tdir/fast_reload_thread.test b/testdata/fast_reload_thread.tdir/fast_reload_thread.test new file mode 100644 index 000000000..d2ef25880 --- /dev/null +++ b/testdata/fast_reload_thread.tdir/fast_reload_thread.test @@ -0,0 +1,38 @@ +# #-- fast_reload_thread.test --# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# use .tpkg.var.test for in test variable passing +[ -f .tpkg.var.test ] && source .tpkg.var.test + +PRE="../.." +. ../common.sh + +echo "> unbound-control status" +$PRE/unbound-control -c ub.conf status +if test $? -ne 0; then + echo "wrong exit value." + exit 1 +else + echo "exit value: OK" +fi + +echo "> unbound-control fast_reload" +$PRE/unbound-control -c ub.conf fast_reload 2>&1 | tee output +if test $? -ne 0; then + echo "wrong exit value." + exit 1 +else + echo "exit value: OK" +fi +wait_logfile unbound.log "start fast reload thread" 60 +wait_logfile unbound.log "stop fast reload thread" 60 +wait_logfile unbound.log "joined with fastreload thread" 60 + +if grep "ok" output; then + echo "OK" +else + echo "output not correct" + exit 1 +fi + +exit 0 diff --git a/util/config_file.c b/util/config_file.c index f6e25a1ea..f763cea32 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -1731,6 +1731,7 @@ config_delete(struct config_file* cfg) config_del_strarray(cfg->tagname, cfg->num_tags); config_del_strbytelist(cfg->local_zone_tags); config_del_strbytelist(cfg->respip_tags); + config_deldblstrlist(cfg->respip_actions); config_deldblstrlist(cfg->acl_view); config_del_strbytelist(cfg->acl_tags); config_deltrplstrlist(cfg->acl_tag_actions); @@ -2834,6 +2835,13 @@ if_is_dnscrypt(const char* ifname, int default_port, int dnscrypt_port) #endif } +size_t +getmem_str(char* str) +{ + if(!str) return 0; + return strlen(str)+1; +} + int if_is_quic(const char* ifname, int default_port, int quic_port) { diff --git a/util/config_file.h b/util/config_file.h index 71e9cad2e..a1dec45b0 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -1454,4 +1454,7 @@ int cfg_has_quic(struct config_file* cfg); #define LINUX_IP_LOCAL_PORT_RANGE_PATH "/proc/sys/net/ipv4/ip_local_port_range" #endif +/** get memory for string */ +size_t getmem_str(char* str); + #endif /* UTIL_CONFIG_FILE_H */ diff --git a/util/edns.c b/util/edns.c index ee95a6912..40b7c9447 100644 --- a/util/edns.c +++ b/util/edns.c @@ -131,6 +131,29 @@ edns_string_addr_lookup(rbtree_type* tree, struct sockaddr_storage* addr, return (struct edns_string_addr*)addr_tree_lookup(tree, addr, addrlen); } +size_t +edns_strings_get_mem(struct edns_strings* edns_strings) +{ + if(!edns_strings) return 0; + return regional_get_mem(edns_strings->region) + sizeof(*edns_strings); +} + +void +edns_strings_swap_tree(struct edns_strings* edns_strings, + struct edns_strings* data) +{ + rbtree_type tree = edns_strings->client_strings; + uint16_t opcode = edns_strings->client_string_opcode; + struct regional* region = edns_strings->region; + + edns_strings->client_strings = data->client_strings; + edns_strings->client_string_opcode = data->client_string_opcode; + edns_strings->region = data->region; + data->client_strings = tree; + data->client_string_opcode = opcode; + data->region = region; +} + uint8_t* edns_cookie_server_hash(const uint8_t* in, const uint8_t* secret, int v4, uint8_t* hash) diff --git a/util/edns.h b/util/edns.h index 47ccb1ad2..5a991fc57 100644 --- a/util/edns.h +++ b/util/edns.h @@ -141,6 +141,22 @@ struct edns_string_addr* edns_string_addr_lookup(rbtree_type* tree, struct sockaddr_storage* addr, socklen_t addrlen); +/** + * Get memory usage of edns strings. + * @param edns_strings: the edns strings + * @return memory usage + */ +size_t edns_strings_get_mem(struct edns_strings* edns_strings); + +/** + * Swap internal tree with preallocated entries. + * @param edns_strings: the edns strings structure. + * @param data: the data structure used to take elements from. This contains + * the old elements on return. + */ +void edns_strings_swap_tree(struct edns_strings* edns_strings, + struct edns_strings* data); + /** * Compute the interoperable DNS cookie (RFC9018) hash. * @param in: buffer input for the hash generation. It needs to be: diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c index e94ec5bbc..c6f3ca24a 100644 --- a/util/fptr_wlist.c +++ b/util/fptr_wlist.c @@ -74,6 +74,7 @@ #include "libunbound/worker.h" #include "util/tube.h" #include "util/config_file.h" +#include "daemon/remote.h" #ifdef UB_ON_WINDOWS #include "winrc/win_svc.h" #endif @@ -121,6 +122,7 @@ fptr_whitelist_comm_point_raw(comm_point_callback_type *fptr) else if(fptr == &tube_handle_write) return 1; else if(fptr == &remote_accept_callback) return 1; else if(fptr == &remote_control_callback) return 1; + else if(fptr == &fast_reload_client_callback) return 1; return 0; } @@ -188,6 +190,7 @@ fptr_whitelist_event(void (*fptr)(int, short, void *)) #ifdef HAVE_NGTCP2 else if(fptr == &comm_point_doq_callback) return 1; #endif + else if(fptr == &fast_reload_service_cb) return 1; #ifdef USE_DNSTAP else if(fptr == &dtio_output_cb) return 1; else if(fptr == &dtio_cmd_cb) return 1; diff --git a/util/module.h b/util/module.h index abad3c8dd..edce4a523 100644 --- a/util/module.h +++ b/util/module.h @@ -177,6 +177,7 @@ struct val_anchors; struct val_neg_cache; struct iter_forwards; struct iter_hints; +struct views; struct respip_set; struct respip_client_info; struct respip_addr_info; @@ -524,6 +525,10 @@ struct module_env { * data structure. */ struct iter_hints* hints; + /** views structure containing view tree */ + struct views* views; + /** response-ip set with associated actions and tags. */ + struct respip_set* respip_set; /** module specific data. indexed by module id. */ void* modinfo[MAX_MODULE]; diff --git a/util/netevent.c b/util/netevent.c index 5e4095061..da8430e41 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -314,6 +314,11 @@ struct ub_event_base* comm_base_internal(struct comm_base* b) return b->eb->base; } +struct ub_event* comm_point_internal(struct comm_point* c) +{ + return c->ev->ev; +} + /** see if errno for udp has to be logged or not uses globals */ static int udp_send_errno_needs_log(struct sockaddr* addr, socklen_t addrlen) @@ -6917,8 +6922,9 @@ comm_timer_is_set(struct comm_timer* timer) } size_t -comm_timer_get_mem(struct comm_timer* ATTR_UNUSED(timer)) +comm_timer_get_mem(struct comm_timer* timer) { + if(!timer) return 0; return sizeof(struct internal_timer); } diff --git a/util/netevent.h b/util/netevent.h index a9deeecea..96de0032c 100644 --- a/util/netevent.h +++ b/util/netevent.h @@ -547,6 +547,14 @@ void comm_base_set_slow_accept_handlers(struct comm_base* b, */ struct ub_event_base* comm_base_internal(struct comm_base* b); +/** + * Access internal event structure. It is for use with + * ub_winsock_tcp_wouldblock on windows. + * @param c: comm point. + * @return event. + */ +struct ub_event* comm_point_internal(struct comm_point* c); + /** * Create an UDP comm point. Calls malloc. * setups the structure with the parameters you provide. diff --git a/util/storage/lruhash.c b/util/storage/lruhash.c index aba9fcc1d..028b199aa 100644 --- a/util/storage/lruhash.c +++ b/util/storage/lruhash.c @@ -562,6 +562,36 @@ lruhash_update_space_used(struct lruhash* table, void* cb_arg, int diff_size) } } +void lruhash_update_space_max(struct lruhash* table, void* cb_arg, size_t max) +{ + struct lruhash_entry *reclaimlist = NULL; + + fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc)); + fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc)); + fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc)); + fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc)); + + if(cb_arg == NULL) cb_arg = table->cb_arg; + + /* update space max */ + lock_quick_lock(&table->lock); + table->space_max = max; + + if(table->space_used > table->space_max) + reclaim_space(table, &reclaimlist); + + lock_quick_unlock(&table->lock); + + /* finish reclaim if any (outside of critical region) */ + while(reclaimlist) { + struct lruhash_entry* n = reclaimlist->overflow_next; + void* d = reclaimlist->data; + (*table->delkeyfunc)(reclaimlist->key, cb_arg); + (*table->deldatafunc)(d, cb_arg); + reclaimlist = n; + } +} + void lruhash_traverse(struct lruhash* h, int wr, void (*func)(struct lruhash_entry*, void*), void* arg) diff --git a/util/storage/lruhash.h b/util/storage/lruhash.h index 5ab488beb..667eba59c 100644 --- a/util/storage/lruhash.h +++ b/util/storage/lruhash.h @@ -314,6 +314,16 @@ void lruhash_setmarkdel(struct lruhash* table, lruhash_markdelfunc_type md); void lruhash_update_space_used(struct lruhash* table, void* cb_override, int diff_size); +/** + * Update the max space for the hashtable. + * + * @param table: hash table. + * @param cb_override: if not NULL overrides the cb_arg for deletefunc. + * @param max: the new max. + */ +void lruhash_update_space_max(struct lruhash* table, void* cb_override, + size_t max); + /************************* getdns functions ************************/ /*** these are used by getdns only and not by unbound. ***/ diff --git a/util/storage/slabhash.c b/util/storage/slabhash.c index 62396e16a..b2bee0838 100644 --- a/util/storage/slabhash.c +++ b/util/storage/slabhash.c @@ -267,3 +267,12 @@ void get_slabhash_stats(struct slabhash* sh, long long* num, long long* collisio if (collisions != NULL) *collisions = max_collisions; } + +void slabhash_adjust_size(struct slabhash* sl, size_t max) +{ + size_t space_max = max / sl->size; + size_t i; + for(i=0; isize; i++) { + lruhash_update_space_max(sl->array[i], NULL, space_max); + } +} diff --git a/util/storage/slabhash.h b/util/storage/slabhash.h index 089847d93..d6d94a940 100644 --- a/util/storage/slabhash.h +++ b/util/storage/slabhash.h @@ -221,6 +221,13 @@ size_t count_slabhash_entries(struct slabhash* table); void get_slabhash_stats(struct slabhash* table, long long* entries_count, long long* max_collisions); +/** + * Adjust size of slabhash memory max + * @param table: slabbed hash table + * @param max: new max memory + */ +void slabhash_adjust_size(struct slabhash* table, size_t max); + /* --- test representation --- */ /** test structure contains test key */ struct slabhash_testkey { diff --git a/util/tcp_conn_limit.c b/util/tcp_conn_limit.c index d7d86a540..284d89076 100644 --- a/util/tcp_conn_limit.c +++ b/util/tcp_conn_limit.c @@ -192,3 +192,14 @@ tcl_list_get_mem(struct tcl_list* tcl) if(!tcl) return 0; return sizeof(*tcl) + regional_get_mem(tcl->region); } + +void tcl_list_swap_tree(struct tcl_list* tcl, struct tcl_list* data) +{ + /* swap tree and region */ + rbtree_type oldtree = tcl->tree; + struct regional* oldregion = tcl->region; + tcl->tree = data->tree; + tcl->region = data->region; + data->tree = oldtree; + data->region = oldregion; +} diff --git a/util/tcp_conn_limit.h b/util/tcp_conn_limit.h index 4fb71a328..52108942c 100644 --- a/util/tcp_conn_limit.h +++ b/util/tcp_conn_limit.h @@ -127,4 +127,13 @@ tcl_addr_lookup(struct tcl_list* tcl, struct sockaddr_storage* addr, */ size_t tcl_list_get_mem(struct tcl_list* tcl); +/** + * Swap internal tree with preallocated entries. Caller should manage + * tcl_addr item locks. + * @param tcl: the tcp connection list structure. + * @param data: the data structure used to take elements from. This contains + * the old elements on return. + */ +void tcl_list_swap_tree(struct tcl_list* tcl, struct tcl_list* data); + #endif /* DAEMON_TCP_CONN_LIMIT_H */ diff --git a/validator/autotrust.c b/validator/autotrust.c index e28d728fd..a0f61885f 100644 --- a/validator/autotrust.c +++ b/validator/autotrust.c @@ -2035,25 +2035,40 @@ wait_probe_time(struct val_anchors* anchors) return 0; } -/** reset worker timer */ +/** reset worker timer, at the time from wait_probe_time. */ static void -reset_worker_timer(struct module_env* env) +reset_worker_timer_at(struct module_env* env, time_t next) { struct timeval tv; #ifndef S_SPLINT_S - time_t next = (time_t)wait_probe_time(env->anchors); /* in case this is libunbound, no timer */ if(!env->probe_timer) return; if(next > *env->now) tv.tv_sec = (time_t)(next - *env->now); else tv.tv_sec = 0; +#else + (void)next; #endif tv.tv_usec = 0; comm_timer_set(env->probe_timer, &tv); verbose(VERB_ALGO, "scheduled next probe in " ARG_LL "d sec", (long long)tv.tv_sec); } +/** reset worker timer. This routine manages the locks on acquiring the + * next time for the timer. */ +static void +reset_worker_timer(struct module_env* env) +{ + time_t next; + if(!env->anchors) + return; + lock_basic_lock(&env->anchors->lock); + next = wait_probe_time(env->anchors); + lock_basic_unlock(&env->anchors->lock); + reset_worker_timer_at(env, next); +} + /** set next probe for trust anchor */ static int set_next_probe(struct module_env* env, struct trust_anchor* tp, @@ -2092,7 +2107,7 @@ set_next_probe(struct module_env* env, struct trust_anchor* tp, verbose(VERB_ALGO, "next probe set in %d seconds", (int)tp->autr->next_probe_time - (int)*env->now); if(mold != mnew) { - reset_worker_timer(env); + reset_worker_timer_at(env, mnew); } return 1; } @@ -2147,7 +2162,7 @@ autr_tp_remove(struct module_env* env, struct trust_anchor* tp, autr_point_delete(del_tp); } if(mold != mnew) { - reset_worker_timer(env); + reset_worker_timer_at(env, mnew); } } diff --git a/validator/val_anchor.c b/validator/val_anchor.c index ab41fa484..daa04504e 100644 --- a/validator/val_anchor.c +++ b/validator/val_anchor.c @@ -1173,17 +1173,53 @@ anchors_lookup(struct val_anchors* anchors, return result; } +/** Get memory usage of assembled key rrset */ +static size_t +assembled_rrset_get_mem(struct ub_packed_rrset_key* pkey) +{ + size_t s; + if(!pkey) + return 0; + s = sizeof(*pkey) + pkey->rk.dname_len; + if(pkey->entry.data) { + struct packed_rrset_data* pd = (struct packed_rrset_data*) + pkey->entry.data; + s += sizeof(*pd) + pd->count * (sizeof(size_t)+sizeof(time_t)+ + sizeof(uint8_t*)); + } + return s; +} + size_t anchors_get_mem(struct val_anchors* anchors) { struct trust_anchor *ta; - size_t s = sizeof(*anchors); - if(!anchors) - return 0; + struct ta_key *k; + size_t s; + if(!anchors) return 0; + s = sizeof(*anchors); + lock_basic_lock(&anchors->lock); RBTREE_FOR(ta, struct trust_anchor*, anchors->tree) { + lock_basic_lock(&ta->lock); s += sizeof(*ta) + ta->namelen; /* keys and so on */ + for(k = ta->keylist; k; k = k->next) { + s += sizeof(*k) + k->len; + } + s += assembled_rrset_get_mem(ta->ds_rrset); + s += assembled_rrset_get_mem(ta->dnskey_rrset); + if(ta->autr) { + struct autr_ta* p; + s += sizeof(*ta->autr); + if(ta->autr->file) + s += strlen(ta->autr->file); + for(p = ta->autr->keys; p; p=p->next) { + s += sizeof(*p) + p->rr_len; + } + } + lock_basic_unlock(&ta->lock); } + lock_basic_unlock(&anchors->lock); return s; } @@ -1346,3 +1382,22 @@ anchors_find_any_noninsecure(struct val_anchors* anchors) lock_basic_unlock(&anchors->lock); return NULL; } + +void +anchors_swap_tree(struct val_anchors* anchors, struct val_anchors* data) +{ + rbtree_type* oldtree; + rbtree_type oldprobe; + + if(!anchors || !data) + return; /* If anchors is NULL, there is no validation. */ + + oldtree = anchors->tree; + oldprobe = anchors->autr->probe; + + anchors->tree = data->tree; + anchors->autr->probe = data->autr->probe; + + data->tree = oldtree; + data->autr->probe = oldprobe; +} diff --git a/validator/val_anchor.h b/validator/val_anchor.h index 02e7e17b5..3fcf70eec 100644 --- a/validator/val_anchor.h +++ b/validator/val_anchor.h @@ -58,7 +58,7 @@ struct sldns_buffer; * on a trust anchor and look it up again to delete it. */ struct val_anchors { - /** lock on trees */ + /** lock on trees. It is locked in order after stubs. */ lock_basic_type lock; /** * Anchors are store in this tree. Sort order is chosen, so that @@ -248,4 +248,12 @@ int anchor_has_keytag(struct val_anchors* anchors, uint8_t* name, int namelabs, */ struct trust_anchor* anchors_find_any_noninsecure(struct val_anchors* anchors); +/** + * Swap internal tree with preallocated entries. + * @param anchors: anchor storage. + * @param data: the data structure used to take elements from. This contains + * the old elements on return. + */ +void anchors_swap_tree(struct val_anchors* anchors, struct val_anchors* data); + #endif /* VALIDATOR_VAL_ANCHOR_H */ diff --git a/validator/val_neg.c b/validator/val_neg.c index b5b678fde..bc3a83aeb 100644 --- a/validator/val_neg.c +++ b/validator/val_neg.c @@ -1554,3 +1554,12 @@ val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo, lock_basic_unlock(&neg->lock); return msg; } + +void +val_neg_adjust_size(struct val_neg_cache* neg, size_t max) +{ + lock_basic_lock(&neg->lock); + neg->max = max; + neg_make_space(neg, 0); + lock_basic_unlock(&neg->lock); +} diff --git a/validator/val_neg.h b/validator/val_neg.h index 5643ca331..27617dee5 100644 --- a/validator/val_neg.h +++ b/validator/val_neg.h @@ -299,4 +299,11 @@ struct val_neg_zone* neg_create_zone(struct val_neg_cache* neg, */ void val_neg_zone_take_inuse(struct val_neg_zone* zone); +/** + * Adjust the size of the negative cache. + * @param neg: negative cache + * @param max: new size for max mem. + */ +void val_neg_adjust_size(struct val_neg_cache* neg, size_t max); + #endif /* VALIDATOR_VAL_NEG_H */ diff --git a/validator/validator.c b/validator/validator.c index 7bfc46737..a0550b484 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -91,50 +91,98 @@ update_reason_bogus(struct reply_info* rep, sldns_ede_code reason_bogus) /** fill up nsec3 key iterations config entry */ static int -fill_nsec3_iter(struct val_env* ve, char* s, int c) +fill_nsec3_iter(size_t** keysize, size_t** maxiter, char* s, int c) { char* e; int i; - free(ve->nsec3_keysize); - free(ve->nsec3_maxiter); - ve->nsec3_keysize = (size_t*)calloc((size_t)c, sizeof(size_t)); - ve->nsec3_maxiter = (size_t*)calloc((size_t)c, sizeof(size_t)); - if(!ve->nsec3_keysize || !ve->nsec3_maxiter) { + *keysize = (size_t*)calloc((size_t)c, sizeof(size_t)); + *maxiter = (size_t*)calloc((size_t)c, sizeof(size_t)); + if(!*keysize || !*maxiter) { + free(*keysize); + *keysize = NULL; + free(*maxiter); + *maxiter = NULL; log_err("out of memory"); return 0; } for(i=0; insec3_keysize[i] = (size_t)strtol(s, &e, 10); + (*keysize)[i] = (size_t)strtol(s, &e, 10); if(s == e) { log_err("cannot parse: %s", s); + free(*keysize); + *keysize = NULL; + free(*maxiter); + *maxiter = NULL; return 0; } s = e; - ve->nsec3_maxiter[i] = (size_t)strtol(s, &e, 10); + (*maxiter)[i] = (size_t)strtol(s, &e, 10); if(s == e) { log_err("cannot parse: %s", s); + free(*keysize); + *keysize = NULL; + free(*maxiter); + *maxiter = NULL; return 0; } s = e; - if(i>0 && ve->nsec3_keysize[i-1] >= ve->nsec3_keysize[i]) { + if(i>0 && (*keysize)[i-1] >= (*keysize)[i]) { log_err("nsec3 key iterations not ascending: %d %d", - (int)ve->nsec3_keysize[i-1], - (int)ve->nsec3_keysize[i]); + (int)(*keysize)[i-1], (int)(*keysize)[i]); + free(*keysize); + *keysize = NULL; + free(*maxiter); + *maxiter = NULL; return 0; } verbose(VERB_ALGO, "validator nsec3cfg keysz %d mxiter %d", - (int)ve->nsec3_keysize[i], (int)ve->nsec3_maxiter[i]); + (int)(*keysize)[i], (int)(*maxiter)[i]); } return 1; } +int +val_env_parse_key_iter(char* val_nsec3_key_iterations, size_t** keysize, + size_t** maxiter, int* keyiter_count) +{ + int c; + c = cfg_count_numbers(val_nsec3_key_iterations); + if(c < 1 || (c&1)) { + log_err("validator: unparsable or odd nsec3 key " + "iterations: %s", val_nsec3_key_iterations); + return 0; + } + *keyiter_count = c/2; + if(!fill_nsec3_iter(keysize, maxiter, val_nsec3_key_iterations, c/2)) { + log_err("validator: cannot apply nsec3 key iterations"); + return 0; + } + return 1; +} + +void +val_env_apply_cfg(struct val_env* val_env, struct config_file* cfg, + size_t* keysize, size_t* maxiter, int keyiter_count) +{ + free(val_env->nsec3_keysize); + free(val_env->nsec3_maxiter); + val_env->nsec3_keysize = keysize; + val_env->nsec3_maxiter = maxiter; + val_env->nsec3_keyiter_count = keyiter_count; + val_env->bogus_ttl = (uint32_t)cfg->bogus_ttl; + val_env->date_override = cfg->val_date_override; + val_env->skew_min = cfg->val_sig_skew_min; + val_env->skew_max = cfg->val_sig_skew_max; + val_env->max_restart = cfg->val_max_restart; +} + /** apply config settings to validator */ static int val_apply_cfg(struct module_env* env, struct val_env* val_env, struct config_file* cfg) { - int c; - val_env->bogus_ttl = (uint32_t)cfg->bogus_ttl; + size_t* keysize=NULL, *maxiter=NULL; + int keyiter_count = 0; if(!env->anchors) env->anchors = anchors_create(); if(!env->anchors) { @@ -154,21 +202,11 @@ val_apply_cfg(struct module_env* env, struct val_env* val_env, log_err("validator: error in trustanchors config"); return 0; } - val_env->date_override = cfg->val_date_override; - val_env->skew_min = cfg->val_sig_skew_min; - val_env->skew_max = cfg->val_sig_skew_max; - val_env->max_restart = cfg->val_max_restart; - c = cfg_count_numbers(cfg->val_nsec3_key_iterations); - if(c < 1 || (c&1)) { - log_err("validator: unparsable or odd nsec3 key " - "iterations: %s", cfg->val_nsec3_key_iterations); - return 0; - } - val_env->nsec3_keyiter_count = c/2; - if(!fill_nsec3_iter(val_env, cfg->val_nsec3_key_iterations, c/2)) { - log_err("validator: cannot apply nsec3 key iterations"); + if(!val_env_parse_key_iter(cfg->val_nsec3_key_iterations, + &keysize, &maxiter, &keyiter_count)) { return 0; } + val_env_apply_cfg(val_env, cfg, keysize, maxiter, keyiter_count); if (env->neg_cache) val_env->neg_cache = env->neg_cache; if(!val_env->neg_cache) diff --git a/validator/validator.h b/validator/validator.h index c07f9d59d..e04fad572 100644 --- a/validator/validator.h +++ b/validator/validator.h @@ -52,6 +52,7 @@ struct key_entry_key; struct val_neg_cache; struct config_strlist; struct comm_timer; +struct config_file; /** * This is the TTL to use when a trust anchor fails to prime. A trust anchor @@ -280,4 +281,26 @@ size_t val_get_mem(struct module_env* env, int id); /** Timer callback for msg signatures continue timer */ void validate_suspend_timer_cb(void* arg); +/** + * Parse the val_nsec3_key_iterations string. + * @param val_nsec3_key_iterations: the string with nsec3 iterations config. + * @param keysize: returns malloced key size array on success. + * @param maxiter: returns malloced max iterations array on success. + * @param keyiter_count: returns size of keysize and maxiter arrays. + * @return false if it does not parse correctly. + */ +int val_env_parse_key_iter(char* val_nsec3_key_iterations, size_t** keysize, + size_t** maxiter, int* keyiter_count); + +/** + * Apply config to validator env + * @param val_env: validator env. + * @param cfg: config + * @param keysize: nsec3 key size array. + * @param maxiter: nsec3 max iterations array. + * @param keyiter_count: size of keysize and maxiter arrays. + */ +void val_env_apply_cfg(struct val_env* val_env, struct config_file* cfg, + size_t* keysize, size_t* maxiter, int keyiter_count); + #endif /* VALIDATOR_VALIDATOR_H */ From 0ca76b05e07cc2f3f7158b0837441cfeb734f927 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Mon, 31 Mar 2025 16:04:07 +0200 Subject: [PATCH 074/218] - Skip the unit tests for auth_tls.tdir and auth_tls_failcert.tdir. --- doc/Changelog | 1 + testdata/auth_tls.tdir/auth_tls.pre | 1 + testdata/auth_tls_failcert.tdir/auth_tls_failcert.pre | 1 + 3 files changed, 3 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 1bebdb7d0..3f97652e1 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -4,6 +4,7 @@ It reads changed config in a thread, then only briefly pauses the service threads, that keep running. DNS service is only interrupted briefly, less than a second. + - Skip the unit tests for auth_tls.tdir and auth_tls_failcert.tdir. 27 March 2025: Wouter - Fix unit test dname log printout typecast. diff --git a/testdata/auth_tls.tdir/auth_tls.pre b/testdata/auth_tls.tdir/auth_tls.pre index ebeee24c5..85ba8384a 100644 --- a/testdata/auth_tls.tdir/auth_tls.pre +++ b/testdata/auth_tls.tdir/auth_tls.pre @@ -5,6 +5,7 @@ [ -f .tpkg.var.test ] && source .tpkg.var.test . ../common.sh +skip_test "Skip test due to no UDP service for SOA query" PRE="../.." if test -n "$NSD"; then : diff --git a/testdata/auth_tls_failcert.tdir/auth_tls_failcert.pre b/testdata/auth_tls_failcert.tdir/auth_tls_failcert.pre index 519c363db..d8f0e624d 100644 --- a/testdata/auth_tls_failcert.tdir/auth_tls_failcert.pre +++ b/testdata/auth_tls_failcert.tdir/auth_tls_failcert.pre @@ -5,6 +5,7 @@ [ -f .tpkg.var.test ] && source .tpkg.var.test . ../common.sh +skip_test "Skip test due to no UDP service for SOA query" PRE="../.." if test -n "$NSD"; then : From 17f95ffac1fb220cc5193bb1304274417fa6cb6d Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 1 Apr 2025 13:53:29 +0200 Subject: [PATCH 075/218] - Fix escape more characters when printing an RR type with an unquoted string. --- doc/Changelog | 4 ++++ sldns/wire2str.c | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index 3f97652e1..23dd28ab0 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +1 April 2025: Wouter + - Fix escape more characters when printing an RR type with an unquoted + string. + 31 March 2025: Wouter - iana portlist update. - Merge #1042: Fast Reload. The unbound-control fast_reload is added. diff --git a/sldns/wire2str.c b/sldns/wire2str.c index f43b47168..1bc5b9cf6 100644 --- a/sldns/wire2str.c +++ b/sldns/wire2str.c @@ -2059,7 +2059,8 @@ int sldns_wire2str_unquoted_scan(uint8_t** d, size_t* dl, char** s, size_t* sl) (*d)++; (*dl)--; for(i=0; i Date: Tue, 1 Apr 2025 14:44:25 +0200 Subject: [PATCH 076/218] - Enable the auth_tls.tdir and auth_tls_failcert.tdir tests. --- doc/Changelog | 1 + testdata/auth_tls.tdir/auth_tls.pre | 2 +- testdata/auth_tls_failcert.tdir/auth_tls_failcert.pre | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 23dd28ab0..ef93fd752 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,7 @@ 1 April 2025: Wouter - Fix escape more characters when printing an RR type with an unquoted string. + - Enable the auth_tls.tdir and auth_tls_failcert.tdir tests. 31 March 2025: Wouter - iana portlist update. diff --git a/testdata/auth_tls.tdir/auth_tls.pre b/testdata/auth_tls.tdir/auth_tls.pre index 85ba8384a..cf652b1dc 100644 --- a/testdata/auth_tls.tdir/auth_tls.pre +++ b/testdata/auth_tls.tdir/auth_tls.pre @@ -5,7 +5,7 @@ [ -f .tpkg.var.test ] && source .tpkg.var.test . ../common.sh -skip_test "Skip test due to no UDP service for SOA query" +#skip_test "Skip test due to no UDP service for SOA query" PRE="../.." if test -n "$NSD"; then : diff --git a/testdata/auth_tls_failcert.tdir/auth_tls_failcert.pre b/testdata/auth_tls_failcert.tdir/auth_tls_failcert.pre index d8f0e624d..56da4af73 100644 --- a/testdata/auth_tls_failcert.tdir/auth_tls_failcert.pre +++ b/testdata/auth_tls_failcert.tdir/auth_tls_failcert.pre @@ -5,7 +5,7 @@ [ -f .tpkg.var.test ] && source .tpkg.var.test . ../common.sh -skip_test "Skip test due to no UDP service for SOA query" +#skip_test "Skip test due to no UDP service for SOA query" PRE="../.." if test -n "$NSD"; then : From 1c58ce07919c36e6a11eead67bd6d14cc22acaec Mon Sep 17 00:00:00 2001 From: "mark.yang" Date: Wed, 2 Apr 2025 15:25:42 +0900 Subject: [PATCH 077/218] fix build with gcc-15 -Wbuiltin-declaration-mismatch error See more details: http://errors.yoctoproject.org/Errors/Details/850313 ../git/compat/malloc.c:9:7: warning: conflicting types for built-in function 'malloc'; expected 'void *(long unsigned int)' [-Wbuiltin-declaration-mismatch] 9 | void *malloc (); | ^~~~~~ ../git/compat/malloc.c:5:1: note: 'malloc' is declared in header '' 4 | #include "config.h" +++ |+#include 5 | #undef malloc ../git/compat/malloc.c: In function 'rpl_malloc_unbound': ../git/compat/malloc.c:23:10: error: too many arguments to function 'malloc'; expected 0, have 1 23 | return malloc (n); | ^~~~~~ ~ ../git/compat/malloc.c:9:7: note: declared here 9 | void *malloc (); | ^~~~~~ * Seeing that there is '#undef malloc', it appears they don't want to use the malloc from stdlib.h. Therefore, we need to correctly define the parameters for malloc. Signed-off-by: mark.yang --- compat/malloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compat/malloc.c b/compat/malloc.c index d8097b13e..af9dcf134 100644 --- a/compat/malloc.c +++ b/compat/malloc.c @@ -6,7 +6,7 @@ #include #ifndef USE_WINSOCK -void *malloc (); +void *malloc (size_t n); #else /* provide a prototype */ void *malloc (size_t n); From fcdb0bbf911ffce7ba9acb03342f244eff841079 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 2 Apr 2025 11:52:04 +0200 Subject: [PATCH 078/218] Changelog entry for #1262: - Merge #1262 from markyang92, fix build with 'gcc-15 -Wbuiltin-declaration-mismatch' error in compat/malloc.c. --- doc/Changelog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index ef93fd752..705dd3f7d 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +2 April 2025: Yorgos + - Merge #1262 from markyang92, fix build with + 'gcc-15 -Wbuiltin-declaration-mismatch' error in compat/malloc.c. + 1 April 2025: Wouter - Fix escape more characters when printing an RR type with an unquoted string. From 8b38fe5ab686c92b276f7ea5652853cb6a38492a Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 2 Apr 2025 11:53:50 +0200 Subject: [PATCH 079/218] - For #1262, ifdef is no longer needed. --- compat/malloc.c | 4 ---- doc/Changelog | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/compat/malloc.c b/compat/malloc.c index af9dcf134..74beae01c 100644 --- a/compat/malloc.c +++ b/compat/malloc.c @@ -5,12 +5,8 @@ #undef malloc #include -#ifndef USE_WINSOCK -void *malloc (size_t n); -#else /* provide a prototype */ void *malloc (size_t n); -#endif /* Allocate an N-byte block of memory from the heap. If N is zero, allocate a 1-byte block. */ diff --git a/doc/Changelog b/doc/Changelog index 705dd3f7d..321a3b9b7 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,7 @@ 2 April 2025: Yorgos - Merge #1262 from markyang92, fix build with 'gcc-15 -Wbuiltin-declaration-mismatch' error in compat/malloc.c. + - For #1262, ifdef is no longer needed. 1 April 2025: Wouter - Fix escape more characters when printing an RR type with an unquoted From a7704ad49f3ff7593f25d7ed28475b72a8b2c6f9 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 2 Apr 2025 15:52:20 +0200 Subject: [PATCH 080/218] - Fix unbound-control test so it counts the new flush_negative output, also answers the _ta probe from testns and prints command output and skip a thread specific test when no threads are available. --- doc/Changelog | 5 +++++ .../09-unbound-control.test | 16 +++++++++++++--- .../09-unbound-control.testns | 8 ++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 321a3b9b7..fff1cc51d 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -3,6 +3,11 @@ 'gcc-15 -Wbuiltin-declaration-mismatch' error in compat/malloc.c. - For #1262, ifdef is no longer needed. +2 April 2025: Wouter + - Fix unbound-control test so it counts the new flush_negative output, + also answers the _ta probe from testns and prints command output + and skip a thread specific test when no threads are available. + 1 April 2025: Wouter - Fix escape more characters when printing an RR type with an unquoted string. diff --git a/testdata/09-unbound-control.tdir/09-unbound-control.test b/testdata/09-unbound-control.tdir/09-unbound-control.test index 80f64c978..da284c2c0 100644 --- a/testdata/09-unbound-control.tdir/09-unbound-control.test +++ b/testdata/09-unbound-control.tdir/09-unbound-control.test @@ -68,9 +68,12 @@ fail_answer () { # Issue an unbound-control command # $@: command arguments -control_command () { +control_command() { echo "$PRE/unbound-control $@" $PRE/unbound-control $@ > outfile + exitstatus=$? + cat outfile + return $exitstatus } # Reload the server and check the reload has finished processing @@ -249,6 +252,10 @@ expect_exit_value 1 teststep "clean reload" clean_reload +# The flush negative only works if the server is either on 1 thread, +# or there is threading enabled. Multiple processes does not work for the +# test, since the printout does not have the stats of a global cache. +if test $num_threads -le 1 -o "$have_threads" = "yes"; then teststep "Check negative flushing" query always.empty. expect_answer "SERVFAIL" @@ -256,10 +263,13 @@ query always.empty. DNSKEY expect_answer "SERVFAIL" control_command -c ub.conf flush_negative expect_exit_value 0 -expect_answer "^ok removed .*, 2 messages and 1 key" +expect_answer "^ok removed .*, 3 messages and 1 key entries" control_command -c ub.conf flush_negative expect_exit_value 0 -expect_answer "^ok removed .*, 0 messages and 0 key" +expect_answer "^ok removed .*, 0 messages and 0 key entries" +else + echo "> skip Check negative flushing, because no threads" +fi teststep "create a new local zone" control_command -c ub.conf local_zone example.net static diff --git a/testdata/09-unbound-control.tdir/09-unbound-control.testns b/testdata/09-unbound-control.tdir/09-unbound-control.testns index 44466b4da..311af0f7e 100644 --- a/testdata/09-unbound-control.tdir/09-unbound-control.testns +++ b/testdata/09-unbound-control.tdir/09-unbound-control.testns @@ -34,3 +34,11 @@ ADJUST copy_id SECTION QUESTION always.empty. IN DNSKEY ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NOERROR +ADJUST copy_id +SECTION QUESTION +_ta-c5aa.always.empty. IN NULL +ENTRY_END From ba18abcd35eb6afc9ec5841d5d385ff4e3faf130 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 2 Apr 2025 16:25:58 +0200 Subject: [PATCH 081/218] - Fix that ub_event has the facility to deal with callbacks for fast reload, doq, windows-stop and dnstap. - Fix fast reload test to check if pid exists before acting on it. --- Makefile.in | 2 +- doc/Changelog | 3 + .../fast_reload_fwd.tdir/fast_reload_fwd.post | 4 +- util/ub_event.c | 55 ++++++++++++++++++- 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Makefile.in b/Makefile.in index 0cdff8ac1..463cdac28 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1064,7 +1064,7 @@ tube.lo tube.o: $(srcdir)/util/tube.c config.h $(srcdir)/util/tube.h $(srcdir)/u $(srcdir)/libunbound/unbound.h $(srcdir)/respip/respip.h $(srcdir)/util/ub_event.h ub_event.lo ub_event.o: $(srcdir)/util/ub_event.c config.h $(srcdir)/util/ub_event.h $(srcdir)/util/log.h \ $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \ - $(srcdir)/util/tube.h $(srcdir)/util/mini_event.h $(srcdir)/util/rbtree.h + $(srcdir)/util/tube.h $(srcdir)/util/mini_event.h $(srcdir)/util/rbtree.h $(srcdir)/daemon/remote.h ub_event_pluggable.lo ub_event_pluggable.o: $(srcdir)/util/ub_event_pluggable.c config.h $(srcdir)/util/ub_event.h \ $(srcdir)/libunbound/unbound-event.h $(srcdir)/util/netevent.h $(srcdir)/dnscrypt/dnscrypt.h \ $(srcdir)/util/log.h $(srcdir)/util/fptr_wlist.h \ diff --git a/doc/Changelog b/doc/Changelog index fff1cc51d..167a2ce42 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -7,6 +7,9 @@ - Fix unbound-control test so it counts the new flush_negative output, also answers the _ta probe from testns and prints command output and skip a thread specific test when no threads are available. + - Fix that ub_event has the facility to deal with callbacks for + fast reload, doq, windows-stop and dnstap. + - Fix fast reload test to check if pid exists before acting on it. 1 April 2025: Wouter - Fix escape more characters when printing an RR type with an unquoted diff --git a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.post b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.post index e7e644b7a..969d0080d 100644 --- a/testdata/fast_reload_fwd.tdir/fast_reload_fwd.post +++ b/testdata/fast_reload_fwd.tdir/fast_reload_fwd.post @@ -10,7 +10,9 @@ PRE="../.." kill_pid $NS1_PID kill_pid $NS2_PID if test -f unbound.pid; then - kill_pid $UNBOUND_PID + if kill -0 $UNBOUND_PID >/dev/null 2>&1; then + kill_pid $UNBOUND_PID + fi fi rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID echo diff --git a/util/ub_event.c b/util/ub_event.c index 8cd87ec4e..93fbaec17 100644 --- a/util/ub_event.c +++ b/util/ub_event.c @@ -46,6 +46,7 @@ #include "util/log.h" #include "util/netevent.h" #include "util/tube.h" +#include "daemon/remote.h" /* We define libevent structures here to hide the libevent stuff. */ @@ -95,9 +96,29 @@ UB_EV_BITS_CB(comm_timer_callback) UB_EV_BITS_CB(comm_signal_callback) UB_EV_BITS_CB(comm_point_local_handle_callback) UB_EV_BITS_CB(comm_point_raw_handle_callback) -UB_EV_BITS_CB(comm_point_http_handle_callback) UB_EV_BITS_CB(tube_handle_signal) UB_EV_BITS_CB(comm_base_handle_slow_accept) +UB_EV_BITS_CB(comm_point_http_handle_callback) +#ifdef HAVE_NGTCP2 +UB_EV_BITS_CB(comm_point_doq_callback) +#endif +UB_EV_BITS_CB(fast_reload_service_cb) +#ifdef USE_DNSTAP +UB_EV_BITS_CB(dtio_output_cb) +UB_EV_BITS_CB(dtio_cmd_cb) +UB_EV_BITS_CB(dtio_reconnect_timeout_cb) +UB_EV_BITS_CB(dtio_stop_timer_cb) +UB_EV_BITS_CB(dtio_stop_ev_cb) +UB_EV_BITS_CB(dtio_tap_callback) +UB_EV_BITS_CB(dtio_mainfdcallback) +#endif +#ifdef HAVE_NGTCP2 +UB_EV_BITS_CB(doq_client_event_cb) +UB_EV_BITS_CB(doq_client_timer_cb) +#endif +#ifdef UB_ON_WINDOWS +UB_EV_BITS_CB(worker_win_stop_cb) +#endif static void (*NATIVE_BITS_CB(void (*cb)(int, short, void*)))(int, short, void*) { @@ -123,6 +144,38 @@ static void (*NATIVE_BITS_CB(void (*cb)(int, short, void*)))(int, short, void*) return my_tube_handle_signal; else if(cb == comm_base_handle_slow_accept) return my_comm_base_handle_slow_accept; +#ifdef HAVE_NGTCP2 + else if(cb == comm_point_doq_callback) + return my_comm_point_doq_callback; +#endif + else if(cb == fast_reload_service_cb) + return my_fast_reload_service_cb; +#ifdef USE_DNSTAP + else if(cb == dtio_output_cb) + return my_dtio_output_cb; + else if(cb == dtio_cmd_cb) + return my_dtio_cmd_cb; + else if(cb == dtio_reconnect_timeout_cb) + return my_dtio_reconnect_timeout_cb; + else if(cb == dtio_stop_timer_cb) + return my_dtio_stop_timer_cb; + else if(cb == dtio_stop_ev_cb) + return my_dtio_stop_ev_cb; + else if(cb == dtio_tap_callback) + return my_dtio_tap_callback; + else if(cb == dtio_mainfdcallback) + return my_dtio_mainfdcallback; +#endif +#ifdef HAVE_NGTCP2 + else if(cb == doq_client_event_cb) + return my_doq_client_event_cb; + else if(cb == doq_client_timer_cb) + return my_doq_client_timer_cb; +#endif +#ifdef UB_ON_WINDOWS + else if(cb == worker_win_stop_cb) + return my_worker_win_stop_cb; +#endif else { log_assert(0); /* this NULL callback pointer should not happen, we should have the necessary routine listed above */ From c2ca679f5c6f0f9ddc65a4075e717f6e98bfc556 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 3 Apr 2025 09:45:36 +0200 Subject: [PATCH 082/218] - Fix #1263: Exempt loopback addresses from wait-limit. --- doc/Changelog | 3 +++ doc/example.conf.in | 6 ++++++ doc/unbound.conf.5.in | 4 ++++ services/cache/infra.c | 31 +++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 167a2ce42..80b6d2404 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +3 April 2025: Wouter + - Fix #1263: Exempt loopback addresses from wait-limit. + 2 April 2025: Yorgos - Merge #1262 from markyang92, fix build with 'gcc-15 -Wbuiltin-declaration-mismatch' error in compat/malloc.c. diff --git a/doc/example.conf.in b/doc/example.conf.in index de73d0044..7aa7bfa6c 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -215,6 +215,12 @@ server: # Apart from the default, the wait limit with cookie can be adjusted. # wait-limit-cookie-netblock: 192.0.2.0/24 50000 + # Defaults for loopback, it has no wait limit. + # wait-limit-netblock: 127.0.0.0/8 -1 + # wait-limit-netblock: ::1/128 -1 + # wait-limit-cookie-netblock: 127.0.0.0/8 -1 + # wait-limit-cookie-netblock: ::1/128 -1 + # the amount of memory to use for the RRset cache. # plain value in bytes or you can append k, m or G. default is "4Mb". # rrset-cache-size: 4m diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 2e8c87e40..1c0e26ce5 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -326,11 +326,15 @@ The wait limit for the netblock. If not given the wait\-limit value is used. The most specific netblock is used to determine the limit. Useful for overriding the default for a specific, group or individual, server. The value -1 disables wait limits for the netblock. +By default the loopback has a wait limit netblock of -1, it is not limited, +because it is separated from the rest of network for spoofed packets. +The loopback addresses 127.0.0.0/8 and ::1/128 are default at -1. .TP .B wait\-limit\-cookie\-netblock: \fI The wait limit for the netblock, when the query has a DNS cookie. If not given, the wait\-limit\-cookie value is used. The value -1 disables wait limits for the netblock. +The loopback addresses 127.0.0.0/8 and ::1/128 are default at -1. .TP .B so\-rcvbuf: \fI If not 0, then set the SO_RCVBUF socket option to get more buffer diff --git a/services/cache/infra.c b/services/cache/infra.c index 9c3e4de43..cf999422d 100644 --- a/services/cache/infra.c +++ b/services/cache/infra.c @@ -297,12 +297,43 @@ infra_wait_limit_netblock_insert(rbtree_type* wait_limits_netblock, return 1; } +/** Add a default wait limit netblock */ +static int +wait_limit_netblock_default(struct rbtree_type* tree, char* str, int limit) +{ + struct wait_limit_netblock_info* d; + d = wait_limit_netblock_findcreate(tree, str); + if(!d) + return 0; + d->limit = limit; + return 1; +} + int setup_wait_limits(rbtree_type* wait_limits_netblock, rbtree_type* wait_limits_cookie_netblock, struct config_file* cfg) { addr_tree_init(wait_limits_netblock); addr_tree_init(wait_limits_cookie_netblock); + + /* Insert defaults */ + /* The loopback address is separated from the rest of the network. */ + /* wait-limit-netblock: 127.0.0.0/8 -1 */ + if(!wait_limit_netblock_default(wait_limits_netblock, "127.0.0.0/8", + -1)) + return 0; + /* wait-limit-netblock: ::1/128 -1 */ + if(!wait_limit_netblock_default(wait_limits_netblock, "::1/128", -1)) + return 0; + /* wait-limit-cookie-netblock: 127.0.0.0/8 -1 */ + if(!wait_limit_netblock_default(wait_limits_cookie_netblock, + "127.0.0.0/8", -1)) + return 0; + /* wait-limit-cookie-netblock: ::1/128 -1 */ + if(!wait_limit_netblock_default(wait_limits_cookie_netblock, + "::1/128", -1)) + return 0; + if(!infra_wait_limit_netblock_insert(wait_limits_netblock, wait_limits_cookie_netblock, cfg)) return 0; From 5f91d5f74c9258ea032f447778ecfea88558bc44 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 3 Apr 2025 09:53:27 +0200 Subject: [PATCH 083/218] - Fix wait-limit-netblock and wait-limit-cookie-netblock config parse to allow two arguments. --- doc/Changelog | 2 ++ util/configlexer.lex | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 80b6d2404..6fbc06a5c 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,5 +1,7 @@ 3 April 2025: Wouter - Fix #1263: Exempt loopback addresses from wait-limit. + - Fix wait-limit-netblock and wait-limit-cookie-netblock config parse + to allow two arguments. 2 April 2025: Yorgos - Merge #1262 from markyang92, fix build with diff --git a/util/configlexer.lex b/util/configlexer.lex index 4c0416f73..f0d470227 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -471,8 +471,8 @@ unknown-server-time-limit{COLON} { YDVAR(1, VAR_UNKNOWN_SERVER_TIME_LIMIT) } discard-timeout{COLON} { YDVAR(1, VAR_DISCARD_TIMEOUT) } wait-limit{COLON} { YDVAR(1, VAR_WAIT_LIMIT) } wait-limit-cookie{COLON} { YDVAR(1, VAR_WAIT_LIMIT_COOKIE) } -wait-limit-netblock{COLON} { YDVAR(1, VAR_WAIT_LIMIT_NETBLOCK) } -wait-limit-cookie-netblock{COLON} { YDVAR(1, VAR_WAIT_LIMIT_COOKIE_NETBLOCK) } +wait-limit-netblock{COLON} { YDVAR(2, VAR_WAIT_LIMIT_NETBLOCK) } +wait-limit-cookie-netblock{COLON} { YDVAR(2, VAR_WAIT_LIMIT_COOKIE_NETBLOCK) } max-udp-size{COLON} { YDVAR(1, VAR_MAX_UDP_SIZE) } dns64-prefix{COLON} { YDVAR(1, VAR_DNS64_PREFIX) } dns64-synthall{COLON} { YDVAR(1, VAR_DNS64_SYNTHALL) } From 9939d5cdb0fc3a4257017171e50c41343c937876 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 3 Apr 2025 10:59:41 +0200 Subject: [PATCH 084/218] - Fix ub_event and include dnstap and win_svc headers. --- doc/Changelog | 1 + util/ub_event.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 6fbc06a5c..a360adfdb 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,7 @@ - Fix #1263: Exempt loopback addresses from wait-limit. - Fix wait-limit-netblock and wait-limit-cookie-netblock config parse to allow two arguments. + - Fix ub_event and include dnstap and win_svc headers. 2 April 2025: Yorgos - Merge #1262 from markyang92, fix build with diff --git a/util/ub_event.c b/util/ub_event.c index 93fbaec17..9133d446c 100644 --- a/util/ub_event.c +++ b/util/ub_event.c @@ -47,6 +47,12 @@ #include "util/netevent.h" #include "util/tube.h" #include "daemon/remote.h" +#ifdef USE_DNSTAP +#include "dnstap/dtstream.h" +#endif +#ifdef UB_ON_WINDOWS +#include "winrc/win_svc.h" +#endif /* We define libevent structures here to hide the libevent stuff. */ From 53ecdb25ef466d8af03b305c99d0cb5419bb8ea3 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 3 Apr 2025 14:50:30 +0200 Subject: [PATCH 085/218] - Fix test for stat_values for wait limit defaults for localhost. --- doc/Changelog | 1 + testdata/stat_values.tdir/stat_values_discard_wait_limit.conf | 2 ++ 2 files changed, 3 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index a360adfdb..9e34b7a1d 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -3,6 +3,7 @@ - Fix wait-limit-netblock and wait-limit-cookie-netblock config parse to allow two arguments. - Fix ub_event and include dnstap and win_svc headers. + - Fix test for stat_values for wait limit defaults for localhost. 2 April 2025: Yorgos - Merge #1262 from markyang92, fix build with diff --git a/testdata/stat_values.tdir/stat_values_discard_wait_limit.conf b/testdata/stat_values.tdir/stat_values_discard_wait_limit.conf index 9e350382d..d4a3459b8 100644 --- a/testdata/stat_values.tdir/stat_values_discard_wait_limit.conf +++ b/testdata/stat_values.tdir/stat_values_discard_wait_limit.conf @@ -18,6 +18,8 @@ server: discard-timeout: 800 wait-limit: 1 + wait-limit-netblock: 127.0.0.0/8 1 + wait-limit-netblock: ::1/128 1 infra-cache-min-rtt: 3000 # This is for the discard-timeout test remote-control: From cb5a019d9f89144cec1768b863a6838bbab645de Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 3 Apr 2025 15:21:16 +0200 Subject: [PATCH 086/218] - Fix parameter unused warning in net_help.c. --- doc/Changelog | 1 + util/net_help.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 9e34b7a1d..9c820e455 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -4,6 +4,7 @@ to allow two arguments. - Fix ub_event and include dnstap and win_svc headers. - Fix test for stat_values for wait limit defaults for localhost. + - Fix parameter unused warning in net_help.c. 2 April 2025: Yorgos - Merge #1262 from markyang92, fix build with diff --git a/util/net_help.c b/util/net_help.c index 81499f228..c57228eee 100644 --- a/util/net_help.c +++ b/util/net_help.c @@ -1393,6 +1393,8 @@ void* listen_sslctx_create(const char* key, const char* pem, return NULL; } } +#else + (void)tls_ciphersuites; /* variable unused. */ #endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */ if(set_ticket_keys_cb) { if(!setup_ticket_keys_cb(ctx)) { From 7fb05c01c2ed5ff36e36a1d7e6693701a8161e7b Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 4 Apr 2025 08:57:24 +0200 Subject: [PATCH 087/218] - Fix mesh_copy_client_info to omit null contents from copy. --- doc/Changelog | 3 +++ services/mesh.c | 42 ++++++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 9c820e455..9e833b87a 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +4 April 2025: Wouter + - Fix mesh_copy_client_info to omit null contents from copy. + 3 April 2025: Wouter - Fix #1263: Exempt loopback addresses from wait-limit. - Fix wait-limit-netblock and wait-limit-cookie-netblock config parse diff --git a/services/mesh.c b/services/mesh.c index e41f1c92b..b62aa5c17 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -919,24 +919,30 @@ mesh_copy_client_info(struct regional* region, struct respip_client_info* cinfo) return NULL; /* Copy the client_info so that if the configuration changes, * then the data stays valid. */ - client_info->taglist = regional_alloc_init(region, cinfo->taglist, - cinfo->taglen); - if(!client_info->taglist) - return NULL; - client_info->tag_actions = regional_alloc_init(region, cinfo->tag_actions, - cinfo->tag_actions_size); - if(!client_info->tag_actions) - return NULL; - client_info->tag_datas = regional_alloc_zero(region, - sizeof(struct config_strlist*)*cinfo->tag_datas_size); - if(!client_info->tag_datas) - return NULL; - for(i=0; itag_datas_size; i++) { - if(cinfo->tag_datas[i]) { - client_info->tag_datas[i] = cfg_region_strlist_copy( - region, cinfo->tag_datas[i]); - if(!client_info->tag_datas[i]) - return NULL; + if(cinfo->taglist) { + client_info->taglist = regional_alloc_init(region, cinfo->taglist, + cinfo->taglen); + if(!client_info->taglist) + return NULL; + } + if(cinfo->tag_actions) { + client_info->tag_actions = regional_alloc_init(region, cinfo->tag_actions, + cinfo->tag_actions_size); + if(!client_info->tag_actions) + return NULL; + } + if(cinfo->tag_datas) { + client_info->tag_datas = regional_alloc_zero(region, + sizeof(struct config_strlist*)*cinfo->tag_datas_size); + if(!client_info->tag_datas) + return NULL; + for(i=0; itag_datas_size; i++) { + if(cinfo->tag_datas[i]) { + client_info->tag_datas[i] = cfg_region_strlist_copy( + region, cinfo->tag_datas[i]); + if(!client_info->tag_datas[i]) + return NULL; + } } } if(cinfo->view) { From 7fb6f9d613082a3ccf625de462a4ee33cb8278bd Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 4 Apr 2025 08:58:54 +0200 Subject: [PATCH 088/218] - Fix comment name in the rpz nsdname test. --- doc/Changelog | 1 + testdata/rpz_nsdname.rpl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index 9e833b87a..6576c3455 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,5 +1,6 @@ 4 April 2025: Wouter - Fix mesh_copy_client_info to omit null contents from copy. + - Fix comment name in the rpz nsdname test. 3 April 2025: Wouter - Fix #1263: Exempt loopback addresses from wait-limit. diff --git a/testdata/rpz_nsdname.rpl b/testdata/rpz_nsdname.rpl index a4e9bb31d..661f5da2c 100644 --- a/testdata/rpz_nsdname.rpl +++ b/testdata/rpz_nsdname.rpl @@ -32,7 +32,7 @@ stub-zone: stub-addr: 1.1.1.1 CONFIG_END -SCENARIO_BEGIN Test RPZ nsip triggers +SCENARIO_BEGIN Test RPZ nsdname triggers ; . -------------------------------------------------------------------------- RANGE_BEGIN 0 100 From 90243a694ad6d316250266c89f66392c37bd6cbe Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 4 Apr 2025 10:20:47 +0200 Subject: [PATCH 089/218] Redis read-only replica support (#1019) * Set version to 1.19.1 for point release. * Initial work for Redis read-only replica support. * Test for Redis replica. * Documentation for the Redis replica timeouts. * redis replica, rewrite set_timeout() * clean merge. * Add new options for fast reload. * Apply suggestions from code review Co-authored-by: Wouter Wijngaards * some more typos --------- Co-authored-by: W.C.A. Wijngaards Co-authored-by: Wouter Wijngaards --- cachedb/redis.c | 224 +++++-- daemon/remote.c | 13 + doc/example.conf.in | 20 +- doc/unbound.conf.5.in | 60 +- testcode/do-tests.sh | 2 + testdata/redis_replica.tdir/after.zone | 2 + testdata/redis_replica.tdir/before.zone | 2 + testdata/redis_replica.tdir/redis.conf | 583 ++++++++++++++++++ .../redis_replica.tdir/redis_replica.conf | 31 + testdata/redis_replica.tdir/redis_replica.dsc | 16 + .../redis_replica.tdir/redis_replica.post | 11 + testdata/redis_replica.tdir/redis_replica.pre | 46 ++ .../redis_replica.tdir/redis_replica.test | 80 +++ .../redis_replica.tdir/unbound_control.key | 39 ++ .../redis_replica.tdir/unbound_control.pem | 22 + .../redis_replica.tdir/unbound_server.key | 39 ++ .../redis_replica.tdir/unbound_server.pem | 22 + util/config_file.c | 19 + util/config_file.h | 8 + util/configlexer.lex | 20 +- util/configparser.y | 132 +++- 21 files changed, 1309 insertions(+), 82 deletions(-) create mode 100644 testdata/redis_replica.tdir/after.zone create mode 100644 testdata/redis_replica.tdir/before.zone create mode 100644 testdata/redis_replica.tdir/redis.conf create mode 100644 testdata/redis_replica.tdir/redis_replica.conf create mode 100644 testdata/redis_replica.tdir/redis_replica.dsc create mode 100644 testdata/redis_replica.tdir/redis_replica.post create mode 100644 testdata/redis_replica.tdir/redis_replica.pre create mode 100644 testdata/redis_replica.tdir/redis_replica.test create mode 100644 testdata/redis_replica.tdir/unbound_control.key create mode 100644 testdata/redis_replica.tdir/unbound_control.pem create mode 100644 testdata/redis_replica.tdir/unbound_server.key create mode 100644 testdata/redis_replica.tdir/unbound_server.pem diff --git a/cachedb/redis.c b/cachedb/redis.c index 183879dd0..3dfa95859 100644 --- a/cachedb/redis.c +++ b/cachedb/redis.c @@ -52,20 +52,38 @@ #include "hiredis/hiredis.h" struct redis_moddata { - redisContext** ctxs; /* thread-specific redis contexts */ - int numctxs; /* number of ctx entries */ - const char* server_host; /* server's IP address or host name */ - int server_port; /* server's TCP port */ - const char* server_path; /* server's unix path, or "", NULL if unused */ - const char* server_password; /* server's AUTH password, or "", NULL if unused */ - struct timeval command_timeout; /* timeout for commands */ - struct timeval connect_timeout; /* timeout for connect */ - int logical_db; /* the redis logical database to use */ - int set_with_ex_available; /* if the SET with EX command is supported */ + /* thread-specific redis contexts */ + redisContext** ctxs; + redisContext** replica_ctxs; + /* number of ctx entries */ + int numctxs; + /* server's IP address or host name */ + const char* server_host; + const char* replica_server_host; + /* server's TCP port */ + int server_port; + int replica_server_port; + /* server's unix path, or "", NULL if unused */ + const char* server_path; + const char* replica_server_path; + /* server's AUTH password, or "", NULL if unused */ + const char* server_password; + const char* replica_server_password; + /* timeout for commands */ + struct timeval command_timeout; + struct timeval replica_command_timeout; + /* timeout for connection setup */ + struct timeval connect_timeout; + struct timeval replica_connect_timeout; + /* the redis logical database to use */ + int logical_db; + int replica_logical_db; + /* if the SET with EX command is supported */ + int set_with_ex_available; }; static redisReply* redis_command(struct module_env*, struct cachedb_env*, - const char*, const uint8_t*, size_t); + const char*, const uint8_t*, size_t, int); static void moddata_clean(struct redis_moddata** moddata) { @@ -79,21 +97,30 @@ moddata_clean(struct redis_moddata** moddata) { } free((*moddata)->ctxs); } + if((*moddata)->replica_ctxs) { + int i; + for(i = 0; i < (*moddata)->numctxs; i++) { + if((*moddata)->replica_ctxs[i]) + redisFree((*moddata)->replica_ctxs[i]); + } + free((*moddata)->replica_ctxs); + } free(*moddata); *moddata = NULL; } static redisContext* -redis_connect(const struct redis_moddata* moddata) +redis_connect(const char* host, int port, const char* path, + const char* password, int logical_db, + const struct timeval connect_timeout, + const struct timeval command_timeout) { redisContext* ctx; - if(moddata->server_path && moddata->server_path[0]!=0) { - ctx = redisConnectUnixWithTimeout(moddata->server_path, - moddata->connect_timeout); + if(path && path[0]!=0) { + ctx = redisConnectUnixWithTimeout(path, connect_timeout); } else { - ctx = redisConnectWithTimeout(moddata->server_host, - moddata->server_port, moddata->connect_timeout); + ctx = redisConnectWithTimeout(host, port, connect_timeout); } if(!ctx || ctx->err) { const char *errstr = "out of memory"; @@ -102,13 +129,13 @@ redis_connect(const struct redis_moddata* moddata) log_err("failed to connect to redis server: %s", errstr); goto fail; } - if(redisSetTimeout(ctx, moddata->command_timeout) != REDIS_OK) { + if(redisSetTimeout(ctx, command_timeout) != REDIS_OK) { log_err("failed to set redis timeout, %s", ctx->errstr); goto fail; } - if(moddata->server_password && moddata->server_password[0]!=0) { + if(password && password[0]!=0) { redisReply* rep; - rep = redisCommand(ctx, "AUTH %s", moddata->server_password); + rep = redisCommand(ctx, "AUTH %s", password); if(!rep || rep->type == REDIS_REPLY_ERROR) { log_err("failed to authenticate with password"); freeReplyObject(rep); @@ -116,18 +143,25 @@ redis_connect(const struct redis_moddata* moddata) } freeReplyObject(rep); } - if(moddata->logical_db > 0) { + if(logical_db > 0) { redisReply* rep; - rep = redisCommand(ctx, "SELECT %d", moddata->logical_db); + rep = redisCommand(ctx, "SELECT %d", logical_db); if(!rep || rep->type == REDIS_REPLY_ERROR) { log_err("failed to set logical database (%d)", - moddata->logical_db); + logical_db); freeReplyObject(rep); goto fail; } freeReplyObject(rep); } - verbose(VERB_OPS, "Connection to Redis established"); + if(verbosity >= VERB_OPS) { + char port_str[6+1]; + port_str[0] = ' '; + (void)snprintf(port_str+1, sizeof(port_str)-1, "%d", port); + verbose(VERB_OPS, "Connection to Redis established (%s%s)", + path&&path[0]!=0?path:host, + path&&path[0]!=0?"":port_str); + } return ctx; fail: @@ -136,6 +170,14 @@ fail: return NULL; } +static void +set_timeout(struct timeval* timeout, int value, int explicit_value) +{ + int v = explicit_value != 0 ? explicit_value : value; + timeout->tv_sec = v / 1000; + timeout->tv_usec = (v % 1000) * 1000; +} + static int redis_init(struct module_env* env, struct cachedb_env* cachedb_env) { @@ -150,38 +192,60 @@ redis_init(struct module_env* env, struct cachedb_env* cachedb_env) goto fail; } moddata->numctxs = env->cfg->num_threads; + /* note: server_host and similar string configuration options are + * shallow references to configured strings; we don't have to free them + * in this module. */ + moddata->server_host = env->cfg->redis_server_host; + moddata->replica_server_host = env->cfg->redis_replica_server_host; + + moddata->server_port = env->cfg->redis_server_port; + moddata->replica_server_port = env->cfg->redis_replica_server_port; + + moddata->server_path = env->cfg->redis_server_path; + moddata->replica_server_path = env->cfg->redis_replica_server_path; + + moddata->server_password = env->cfg->redis_server_password; + moddata->replica_server_password = env->cfg->redis_replica_server_password; + + set_timeout(&moddata->command_timeout, + env->cfg->redis_timeout, + env->cfg->redis_command_timeout); + set_timeout(&moddata->replica_command_timeout, + env->cfg->redis_replica_timeout, + env->cfg->redis_replica_command_timeout); + set_timeout(&moddata->connect_timeout, + env->cfg->redis_timeout, + env->cfg->redis_connect_timeout); + set_timeout(&moddata->replica_connect_timeout, + env->cfg->redis_replica_timeout, + env->cfg->redis_replica_connect_timeout); + + moddata->logical_db = env->cfg->redis_logical_db; + moddata->replica_logical_db = env->cfg->redis_replica_logical_db; + moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*)); if(!moddata->ctxs) { log_err("out of memory"); goto fail; } - /* note: server_host is a shallow reference to configured string. - * we don't have to free it in this module. */ - moddata->server_host = env->cfg->redis_server_host; - moddata->server_port = env->cfg->redis_server_port; - moddata->server_path = env->cfg->redis_server_path; - moddata->server_password = env->cfg->redis_server_password; - moddata->command_timeout.tv_sec = env->cfg->redis_timeout / 1000; - moddata->command_timeout.tv_usec = - (env->cfg->redis_timeout % 1000) * 1000; - moddata->connect_timeout.tv_sec = env->cfg->redis_timeout / 1000; - moddata->connect_timeout.tv_usec = - (env->cfg->redis_timeout % 1000) * 1000; - if(env->cfg->redis_command_timeout != 0) { - moddata->command_timeout.tv_sec = - env->cfg->redis_command_timeout / 1000; - moddata->command_timeout.tv_usec = - (env->cfg->redis_command_timeout % 1000) * 1000; + if((moddata->replica_server_host && moddata->replica_server_host[0]!=0) + || (moddata->replica_server_path && moddata->replica_server_path[0]!=0)) { + /* There is a replica configured, allocate ctxs */ + moddata->replica_ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*)); + if(!moddata->replica_ctxs) { + log_err("out of memory"); + goto fail; + } } - if(env->cfg->redis_connect_timeout != 0) { - moddata->connect_timeout.tv_sec = - env->cfg->redis_connect_timeout / 1000; - moddata->connect_timeout.tv_usec = - (env->cfg->redis_connect_timeout % 1000) * 1000; - } - moddata->logical_db = env->cfg->redis_logical_db; for(i = 0; i < moddata->numctxs; i++) { - redisContext* ctx = redis_connect(moddata); + redisContext* ctx = redis_connect( + moddata->server_host, + moddata->server_port, + moddata->server_path, + moddata->server_password, + moddata->logical_db, + moddata->connect_timeout, + moddata->command_timeout); if(!ctx) { log_err("redis_init: failed to init redis " "(for thread %d)", i); @@ -190,6 +254,25 @@ redis_init(struct module_env* env, struct cachedb_env* cachedb_env) } moddata->ctxs[i] = ctx; } + if(moddata->replica_ctxs) { + for(i = 0; i < moddata->numctxs; i++) { + redisContext* ctx = redis_connect( + moddata->replica_server_host, + moddata->replica_server_port, + moddata->replica_server_path, + moddata->replica_server_password, + moddata->replica_logical_db, + moddata->replica_connect_timeout, + moddata->replica_command_timeout); + if(!ctx) { + log_err("redis_init: failed to init redis " + "replica (for thread %d)", i); + /* And continue, the context can be established + * later, just like after a disconnect. */ + } + moddata->replica_ctxs[i] = ctx; + } + } cachedb_env->backend_data = moddata; if(env->cfg->redis_expire_records && moddata->ctxs[env->alloc->thread_num] != NULL) { @@ -197,7 +280,7 @@ redis_init(struct module_env* env, struct cachedb_env* cachedb_env) int redis_reply_type = 0; /** check if set with ex command is supported */ rep = redis_command(env, cachedb_env, - "SET __UNBOUND_REDIS_CHECK__ none EX 1", NULL, 0); + "SET __UNBOUND_REDIS_CHECK__ none EX 1", NULL, 0, 1); if(!rep) { /** init failed, no response from redis server*/ goto set_with_ex_fail; @@ -250,9 +333,9 @@ redis_deinit(struct module_env* env, struct cachedb_env* cachedb_env) */ static redisReply* redis_command(struct module_env* env, struct cachedb_env* cachedb_env, - const char* command, const uint8_t* data, size_t data_len) + const char* command, const uint8_t* data, size_t data_len, int write) { - redisContext* ctx; + redisContext* ctx, **ctx_selector; redisReply* rep; struct redis_moddata* d = (struct redis_moddata*) cachedb_env->backend_data; @@ -263,17 +346,38 @@ redis_command(struct module_env* env, struct cachedb_env* cachedb_env, * assumption throughout the unbound architecture, so we simply assert * it. */ log_assert(env->alloc->thread_num < d->numctxs); - ctx = d->ctxs[env->alloc->thread_num]; + + ctx_selector = !write && d->replica_ctxs + ?d->replica_ctxs + :d->ctxs; + ctx = ctx_selector[env->alloc->thread_num]; /* If we've not established a connection to the server or we've closed * it on a failure, try to re-establish a new one. Failures will be * logged in redis_connect(). */ if(!ctx) { - ctx = redis_connect(d); - d->ctxs[env->alloc->thread_num] = ctx; + if(!write && d->replica_ctxs) { + ctx = redis_connect( + d->replica_server_host, + d->replica_server_port, + d->replica_server_path, + d->replica_server_password, + d->replica_logical_db, + d->replica_connect_timeout, + d->replica_command_timeout); + } else { + ctx = redis_connect( + d->server_host, + d->server_port, + d->server_path, + d->server_password, + d->logical_db, + d->connect_timeout, + d->command_timeout); + } + ctx_selector[env->alloc->thread_num] = ctx; } - if(!ctx) - return NULL; + if(!ctx) return NULL; /* Send the command and get a reply, synchronously. */ rep = (redisReply*)redisCommand(ctx, command, data, data_len); @@ -283,7 +387,7 @@ redis_command(struct module_env* env, struct cachedb_env* cachedb_env, log_err("redis_command: failed to receive a reply, " "closing connection: %s", ctx->errstr); redisFree(ctx); - d->ctxs[env->alloc->thread_num] = NULL; + ctx_selector[env->alloc->thread_num] = NULL; return NULL; } @@ -313,7 +417,7 @@ redis_lookup(struct module_env* env, struct cachedb_env* cachedb_env, return 0; } - rep = redis_command(env, cachedb_env, cmdbuf, NULL, 0); + rep = redis_command(env, cachedb_env, cmdbuf, NULL, 0, 0); if(!rep) return 0; switch(rep->type) { @@ -391,7 +495,7 @@ redis_store(struct module_env* env, struct cachedb_env* cachedb_env, return; } - rep = redis_command(env, cachedb_env, cmdbuf, data, data_len); + rep = redis_command(env, cachedb_env, cmdbuf, data, data_len, 1); if(rep) { verbose(VERB_ALGO, "redis_store set completed"); if(rep->type != REDIS_REPLY_STATUS && diff --git a/daemon/remote.c b/daemon/remote.c index b4b8abe25..df1f0d4e6 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -4903,8 +4903,11 @@ config_file_getmem(struct config_file* cfg) m += getmem_str(cfg->cachedb_secret); #ifdef USE_REDIS m += getmem_str(cfg->redis_server_host); + m += getmem_str(cfg->redis_replica_server_host); m += getmem_str(cfg->redis_server_path); + m += getmem_str(cfg->redis_replica_server_path); m += getmem_str(cfg->redis_server_password); + m += getmem_str(cfg->redis_replica_server_password); #endif #endif #ifdef USE_IPSET @@ -5779,12 +5782,22 @@ fr_atomic_copy_cfg(struct config_file* oldcfg, struct config_file* cfg, COPY_VAR_int(cachedb_check_when_serve_expired); #ifdef USE_REDIS COPY_VAR_ptr(redis_server_host); + COPY_VAR_ptr(redis_replica_server_host); COPY_VAR_int(redis_server_port); + COPY_VAR_int(redis_replica_server_port); COPY_VAR_ptr(redis_server_path); + COPY_VAR_ptr(redis_replica_server_path); COPY_VAR_ptr(redis_server_password); + COPY_VAR_ptr(redis_replica_server_password); COPY_VAR_int(redis_timeout); + COPY_VAR_int(redis_replica_timeout); + COPY_VAR_int(redis_command_timeout); + COPY_VAR_int(redis_replica_command_timeout); + COPY_VAR_int(redis_connect_timeout); + COPY_VAR_int(redis_replica_connect_timeout); COPY_VAR_int(redis_expire_records); COPY_VAR_int(redis_logical_db); + COPY_VAR_int(redis_replica_logical_db); #endif #endif COPY_VAR_int(do_answer_cookie); diff --git a/doc/example.conf.in b/doc/example.conf.in index 7aa7bfa6c..6eabbe5fd 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -1313,9 +1313,9 @@ remote-control: # # redis server's TCP port # redis-server-port: 6379 # # if the server uses a unix socket, set its path, or "" when not used. -# # redis-server-path: "/var/lib/redis/redis-server.sock" +# redis-server-path: "/var/lib/redis/redis-server.sock" # # if the server uses an AUTH password, specify here, or "" when not used. -# # redis-server-password: "" +# redis-server-password: "" # # timeout (in ms) for communication with the redis server # redis-timeout: 100 # # timeout (in ms) for commands, if 0, uses redis-timeout. @@ -1326,6 +1326,22 @@ remote-control: # redis-expire-records: no # # redis logical database to use, 0 is the default database. # redis-logical-db: 0 +# # redis replica server's IP address or host name +# redis-replica-server-host: 127.0.0.1 +# # redis replica server's TCP port +# redis-replica-server-port: 6379 +# # if the replica server uses a unix socket, set its path, or "" when not used. +# redis-replica-server-path: "/var/lib/redis/redis-server.sock" +# # if the replica server uses an AUTH password, specify here, or "" when not used. +# redis-replica-server-password: "" +# # timeout (in ms) for communication with the redis replica server +# redis-replica-timeout: 100 +# # timeout (in ms) for redis replica commands, if 0, uses redis-replica-timeout. +# redis-replica-command-timeout: 0 +# # timeout (in ms) for redis replica connection set up, if 0, uses redis-replica-timeout. +# redis-replica-connect-timeout: 0 +# # redis logical database to use for the replica server, 0 is the default database. +# redis-replica-logical-db: 0 # IPSet # Add specify domain into set via ipset. diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 1c0e26ce5..e65125a63 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -2860,12 +2860,12 @@ The TCP port number of the Redis server. This option defaults to 6379. .TP .B redis-server-path: \fI\fR -The unix socket path to connect to the redis server. Off by default, and it +The unix socket path to connect to the Redis server. Off by default, and it can be set to "" to turn this off. Unix sockets may have better throughput than the IP address option. .TP .B redis-server-password: \fI""\fR -The Redis AUTH password to use for the redis server. +The Redis AUTH password to use for the Redis server. Only relevant if Redis is configured for client password authorisation. Off by default, and it can be set to "" to turn this off. .TP @@ -2877,12 +2877,14 @@ re-establish a new connection later. This option defaults to 100 milliseconds. .TP .B redis-command-timeout: \fI\fR -The timeout to use for redis commands, in milliseconds. If 0, it uses the -redis\-timeout value. The default is 0. +The timeout to use for Redis commands, in milliseconds. +If 0, it uses the \fBredis\-timeout\fR value. +The default is 0. .TP .B redis-connect-timeout: \fI\fR -The timeout to use for redis connection set up, in milliseconds. If 0, it -uses the redis\-timeout value. The default is 0. +The timeout to use for Redis connection set up, in milliseconds. +If 0, it uses the \fBredis\-timeout\fR value. +The default is 0. .TP .B redis-expire-records: \fI If Redis record expiration is enabled. If yes, Unbound sets timeout for Redis @@ -2902,6 +2904,52 @@ for multiple unrelated applications. The default database in Redis is 0 while other logical databases need to be explicitly SELECT'ed upon connecting. This option defaults to 0. +.TP +.B redis-replica-server-host: \fI\fR +The IP (either v6 or v4) address or domain name of the Redis replica server. +In general an IP address should be specified as otherwise Unbound will have to +resolve the name of the server every time it establishes a connection +to the server. +This server is treated as a read-only replica server +(https://redis.io/docs/management/replication/#read-only-replica). +If specified, all Redis read commands will go to this replica server, while +the write commands will go to the \fBredis-server-host\fR. +This option defaults to "" (disabled). +.TP +.B redis-replica-server-port: \fI\fR +The TCP port number of the Redis replica server. +This option defaults to 6379. +.TP +.B redis-replica-server-path: \fI\fR +The unix socket path to connect to the Redis server. Off by default, and it +can be set to "" to turn this off. Unix sockets may have better throughput +than the IP address option. +.TP +.B redis-replica-server-password: \fI""\fR +The Redis AUTH password to use for the Redis replica server. +Only relevant if Redis is configured for client password authorisation. +Off by default, and it can be set to "" to turn this off. +.TP +.B redis-replica-timeout: \fI\fR +The period until when Unbound waits for a response from the Redis replica sever. +If this timeout expires Unbound closes the connection, treats it as +if the Redis replica server does not have the requested data, and will try to +re-establish a new connection later. +This option defaults to 100 milliseconds. +.TP +.B redis-replica-command-timeout: \fI\fR +The timeout to use for Redis replica commands, in milliseconds. +If 0, it uses the \fBredis\-replica\-timeout\fR value. +The default is 0. +.TP +.B redis-replica-connect-timeout: \fI\fR +The timeout to use for Redis replica connection set up, in milliseconds. +If 0, it uses the \fBredis\-replica\-timeout\fR value. +The default is 0. +.TP +.B redis-replica-logical-db: \fI +Same as \fBredis-logical-db\fR but for the Redis replica server. +This option defaults to 0. .SS DNSTAP Logging Options DNSTAP support, when compiled in by using \fB\-\-enable\-dnstap\fR, is enabled in the \fBdnstap:\fR section. diff --git a/testcode/do-tests.sh b/testcode/do-tests.sh index 6599f9f66..4f769c5fd 100755 --- a/testcode/do-tests.sh +++ b/testcode/do-tests.sh @@ -17,6 +17,7 @@ NEED_IPV6='fwd_ancil.tdir fwd_tcp_tc6.tdir stub_udp6.tdir edns_cache.tdir' NEED_NOMINGW='tcp_sigpipe.tdir 07-confroot.tdir 08-host-lib.tdir fwd_ancil.tdir' NEED_DNSCRYPT_PROXY='dnscrypt_queries.tdir dnscrypt_queries_chacha.tdir' NEED_UNSHARE='acl_interface.tdir proxy_protocol.tdir' +NEED_REDIS_SERVER='redis_replica.tdir' # test if dig and ldns-testns are available. test_tool_avail "dig" @@ -52,6 +53,7 @@ for test in `ls -d *.tdir`; do skip_if_in_list $test "$NEED_WHOAMI" "whoami" skip_if_in_list $test "$NEED_DNSCRYPT_PROXY" "dnscrypt-proxy" skip_if_in_list $test "$NEED_UNSHARE" "unshare" + skip_if_in_list $test "$NEED_REDIS_SERVER" "redis-server" if echo $NEED_IPV6 | grep $test >/dev/null; then if test "$HAVE_IPV6" = no; then diff --git a/testdata/redis_replica.tdir/after.zone b/testdata/redis_replica.tdir/after.zone new file mode 100644 index 000000000..11c268f81 --- /dev/null +++ b/testdata/redis_replica.tdir/after.zone @@ -0,0 +1,2 @@ +redis.com. IN SOA server. ma.il 1 2 3 4 5 +redis.com. IN A 2.2.2.2 diff --git a/testdata/redis_replica.tdir/before.zone b/testdata/redis_replica.tdir/before.zone new file mode 100644 index 000000000..8e50c6267 --- /dev/null +++ b/testdata/redis_replica.tdir/before.zone @@ -0,0 +1,2 @@ +redis.com. IN SOA server. ma.il 1 2 3 4 5 +redis.com. IN A 1.1.1.1 diff --git a/testdata/redis_replica.tdir/redis.conf b/testdata/redis_replica.tdir/redis.conf new file mode 100644 index 000000000..20a3133f8 --- /dev/null +++ b/testdata/redis_replica.tdir/redis.conf @@ -0,0 +1,583 @@ +### +### Settings for this test ################################################### +### + +# Accept connections on the specified port, default is 6379 (IANA #815344). +# If port 0 is specified Redis will not listen on a TCP socket. +port 0 + +# Unix socket. +# +# Specify the path for the Unix socket that will be used to listen for +# incoming connections. There is no default, so Redis will not listen +# on a unix socket when not specified. +# +unixsocket @SOCKET@ +# unixsocketperm 700 + +# By default Redis does not run as a daemon. Use 'yes' if you need it. +# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. +# When Redis is supervised by upstart or systemd, this parameter has no impact. +daemonize no + +# Specify the server verbosity level. +# This can be one of: +# debug (a lot of information, useful for development/testing) +# verbose (many rarely useful info, but not a mess like the debug level) +# notice (moderately verbose, what you want in production probably) +# warning (only very important / critical messages are logged) +# nothing (nothing is logged) +loglevel notice + +# Specify the log file name. Also the empty string can be used to force +# Redis to log on the standard output. Note that if you use standard +# output for logging but daemonize, logs will be sent to /dev/null +logfile @LOGFILE@ + +# To enable logging to the system logger, just set 'syslog-enabled' to yes, +# and optionally update the other syslog parameters to suit your needs. +syslog-enabled no + +# Set the number of databases. The default database is DB 0, you can select +# a different one on a per-connection basis using SELECT where +# dbid is a number between 0 and 'databases'-1 +databases 2 + +# Snapshotting can be completely disabled with a single empty string argument +# as in following example: +# +save "" + +# The working directory. +# +# The DB will be written inside this directory, with the filename specified +# above using the 'dbfilename' configuration directive. +# +# The Append Only File will also be created inside this directory. +# +# Note that you must specify a directory here, not a file name. +dir . + +### +### Rest of the default Redis settings ####################################### +### + +bind 127.0.0.1 -::1 + +# When protected mode is on and the default user has no password, the server +# only accepts local connections from the IPv4 address (127.0.0.1), IPv6 address +# (::1) or Unix domain sockets. +protected-mode yes + +# TCP listen() backlog. +# +# In high requests-per-second environments you need a high backlog in order +# to avoid slow clients connection issues. Note that the Linux kernel +# will silently truncate it to the value of /proc/sys/net/core/somaxconn so +# make sure to raise both the value of somaxconn and tcp_max_syn_backlog +# in order to get the desired effect. +tcp-backlog 511 + +# Close the connection after a client is idle for N seconds (0 to disable) +timeout 0 + +# TCP keepalive. +# A reasonable value for this option is 300 seconds, which is the new +# Redis default starting with Redis 3.2.1. +tcp-keepalive 300 + +# By default Redis shows an ASCII art logo only when started to log to the +# standard output and if the standard output is a TTY and syslog logging is +# disabled. Basically this means that normally a logo is displayed only in +# interactive sessions. +# +# However it is possible to force the pre-4.0 behavior and always show a +# ASCII art logo in startup logs by setting the following option to yes. +always-show-logo no + +# By default, Redis modifies the process title (as seen in 'top' and 'ps') to +# provide some runtime information. It is possible to disable this and leave +# the process name as executed by setting the following to no. +set-proc-title yes + +# When changing the process title, Redis uses the following template to construct +# the modified title. +# +# Template variables are specified in curly brackets. The following variables are +# supported: +# +# {title} Name of process as executed if parent, or type of child process. +# {listen-addr} Bind address or '*' followed by TCP or TLS port listening on, or +# Unix socket if only that's available. +# {server-mode} Special mode, i.e. "[sentinel]" or "[cluster]". +# {port} TCP port listening on, or 0. +# {tls-port} TLS port listening on, or 0. +# {unixsocket} Unix domain socket listening on, or "". +# {config-file} Name of configuration file used. +# +proc-title-template "{title} {listen-addr} {server-mode}" + +# Set the local environment which is used for string comparison operations, and +# also affect the performance of Lua scripts. Empty String indicates the locale +# is derived from the environment variables. +locale-collate "" + +# By default Redis will stop accepting writes if RDB snapshots are enabled +# (at least one save point) and the latest background save failed. +# This will make the user aware (in a hard way) that data is not persisting +# on disk properly, otherwise chances are that no one will notice and some +# disaster will happen. +# +# If the background saving process will start working again Redis will +# automatically allow writes again. +# +# However if you have setup your proper monitoring of the Redis server +# and persistence, you may want to disable this feature so that Redis will +# continue to work as usual even if there are problems with disk, +# permissions, and so forth. +stop-writes-on-bgsave-error yes + +# Compress string objects using LZF when dump .rdb databases? +# By default compression is enabled as it's almost always a win. +# If you want to save some CPU in the saving child set it to 'no' but +# the dataset will likely be bigger if you have compressible values or keys. +rdbcompression yes + +# Since version 5 of RDB a CRC64 checksum is placed at the end of the file. +# This makes the format more resistant to corruption but there is a performance +# hit to pay (around 10%) when saving and loading RDB files, so you can disable it +# for maximum performances. +# +# RDB files created with checksum disabled have a checksum of zero that will +# tell the loading code to skip the check. +rdbchecksum yes + +# The filename where to dump the DB +dbfilename redis.rdb + +# Remove RDB files used by replication in instances without persistence +# enabled. By default this option is disabled, however there are environments +# where for regulations or other security concerns, RDB files persisted on +# disk by masters in order to feed replicas, or stored on disk by replicas +# in order to load them for the initial synchronization, should be deleted +# ASAP. Note that this option ONLY WORKS in instances that have both AOF +# and RDB persistence disabled, otherwise is completely ignored. +# +# An alternative (and sometimes better) way to obtain the same effect is +# to use diskless replication on both master and replicas instances. However +# in the case of replicas, diskless is not always an option. +rdb-del-sync-files no + +# When a replica loses its connection with the master, or when the replication +# is still in progress, the replica can act in two different ways: +# +# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will +# still reply to client requests, possibly with out of date data, or the +# data set may just be empty if this is the first synchronization. +# +# 2) If replica-serve-stale-data is set to 'no' the replica will reply with error +# "MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'" +# to all data access commands, excluding commands such as: +# INFO, REPLICAOF, AUTH, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, +# UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, +# HOST and LATENCY. +# +replica-serve-stale-data yes + +# You can configure a replica instance to accept writes or not. Writing against +# a replica instance may be useful to store some ephemeral data (because data +# written on a replica will be easily deleted after resync with the master) but +# may also cause problems if clients are writing to it because of a +# misconfiguration. +# +# Since Redis 2.6 by default replicas are read-only. +# +# Note: read only replicas are not designed to be exposed to untrusted clients +# on the internet. It's just a protection layer against misuse of the instance. +# Still a read only replica exports by default all the administrative commands +# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve +# security of read only replicas using 'rename-command' to shadow all the +# administrative / dangerous commands. +replica-read-only yes + +# Replication SYNC strategy: disk or socket. +# +# New replicas and reconnecting replicas that are not able to continue the +# replication process just receiving differences, need to do what is called a +# "full synchronization". An RDB file is transmitted from the master to the +# replicas. +# +# The transmission can happen in two different ways: +# +# 1) Disk-backed: The Redis master creates a new process that writes the RDB +# file on disk. Later the file is transferred by the parent +# process to the replicas incrementally. +# 2) Diskless: The Redis master creates a new process that directly writes the +# RDB file to replica sockets, without touching the disk at all. +# +# With disk-backed replication, while the RDB file is generated, more replicas +# can be queued and served with the RDB file as soon as the current child +# producing the RDB file finishes its work. With diskless replication instead +# once the transfer starts, new replicas arriving will be queued and a new +# transfer will start when the current one terminates. +# +# When diskless replication is used, the master waits a configurable amount of +# time (in seconds) before starting the transfer in the hope that multiple +# replicas will arrive and the transfer can be parallelized. +# +# With slow disks and fast (large bandwidth) networks, diskless replication +# works better. +repl-diskless-sync yes + +# When diskless replication is enabled, it is possible to configure the delay +# the server waits in order to spawn the child that transfers the RDB via socket +# to the replicas. +# +# This is important since once the transfer starts, it is not possible to serve +# new replicas arriving, that will be queued for the next RDB transfer, so the +# server waits a delay in order to let more replicas arrive. +# +# The delay is specified in seconds, and by default is 5 seconds. To disable +# it entirely just set it to 0 seconds and the transfer will start ASAP. +repl-diskless-sync-delay 5 + +# When diskless replication is enabled with a delay, it is possible to let +# the replication start before the maximum delay is reached if the maximum +# number of replicas expected have connected. Default of 0 means that the +# maximum is not defined and Redis will wait the full delay. +repl-diskless-sync-max-replicas 0 + +# ----------------------------------------------------------------------------- +# WARNING: Since in this setup the replica does not immediately store an RDB on +# disk, it may cause data loss during failovers. RDB diskless load + Redis +# modules not handling I/O reads may cause Redis to abort in case of I/O errors +# during the initial synchronization stage with the master. +# ----------------------------------------------------------------------------- +# +# Replica can load the RDB it reads from the replication link directly from the +# socket, or store the RDB to a file and read that file after it was completely +# received from the master. +# +# In many cases the disk is slower than the network, and storing and loading +# the RDB file may increase replication time (and even increase the master's +# Copy on Write memory and replica buffers). +# However, when parsing the RDB file directly from the socket, in order to avoid +# data loss it's only safe to flush the current dataset when the new dataset is +# fully loaded in memory, resulting in higher memory usage. +# For this reason we have the following options: +# +# "disabled" - Don't use diskless load (store the rdb file to the disk first) +# "swapdb" - Keep current db contents in RAM while parsing the data directly +# from the socket. Replicas in this mode can keep serving current +# dataset while replication is in progress, except for cases where +# they can't recognize master as having a data set from same +# replication history. +# Note that this requires sufficient memory, if you don't have it, +# you risk an OOM kill. +# "on-empty-db" - Use diskless load only when current dataset is empty. This is +# safer and avoid having old and new dataset loaded side by side +# during replication. +repl-diskless-load disabled + +# Master send PINGs to its replicas in a predefined interval. It's possible to +# change this interval with the repl_ping_replica_period option. The default +# value is 10 seconds. +# +# repl-ping-replica-period 10 + +# The following option sets the replication timeout for: +# +# 1) Bulk transfer I/O during SYNC, from the point of view of replica. +# 2) Master timeout from the point of view of replicas (data, pings). +# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings). +# +# It is important to make sure that this value is greater than the value +# specified for repl-ping-replica-period otherwise a timeout will be detected +# every time there is low traffic between the master and the replica. The default +# value is 60 seconds. +# +# repl-timeout 60 + +# Disable TCP_NODELAY on the replica socket after SYNC? +# +# If you select "yes" Redis will use a smaller number of TCP packets and +# less bandwidth to send data to replicas. But this can add a delay for +# the data to appear on the replica side, up to 40 milliseconds with +# Linux kernels using a default configuration. +# +# If you select "no" the delay for data to appear on the replica side will +# be reduced but more bandwidth will be used for replication. +# +# By default we optimize for low latency, but in very high traffic conditions +# or when the master and replicas are many hops away, turning this to "yes" may +# be a good idea. +repl-disable-tcp-nodelay no + +# The replica priority is an integer number published by Redis in the INFO +# output. It is used by Redis Sentinel in order to select a replica to promote +# into a master if the master is no longer working correctly. +# +# A replica with a low priority number is considered better for promotion, so +# for instance if there are three replicas with priority 10, 100, 25 Sentinel +# will pick the one with priority 10, that is the lowest. +# +# However a special priority of 0 marks the replica as not able to perform the +# role of master, so a replica with priority of 0 will never be selected by +# Redis Sentinel for promotion. +# +# By default the priority is 100. +replica-priority 100 + +# ACL LOG +# +# The ACL Log tracks failed commands and authentication events associated +# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked +# by ACLs. The ACL Log is stored in memory. You can reclaim memory with +# ACL LOG RESET. Define the maximum entry length of the ACL Log below. +acllog-max-len 128 + +lazyfree-lazy-eviction no +lazyfree-lazy-expire no +lazyfree-lazy-server-del no +replica-lazy-flush no + +# It is also possible, for the case when to replace the user code DEL calls +# with UNLINK calls is not easy, to modify the default behavior of the DEL +# command to act exactly like UNLINK, using the following configuration +# directive: +lazyfree-lazy-user-del no + +# FLUSHDB, FLUSHALL, SCRIPT FLUSH and FUNCTION FLUSH support both asynchronous and synchronous +# deletion, which can be controlled by passing the [SYNC|ASYNC] flags into the +# commands. When neither flag is passed, this directive will be used to determine +# if the data should be deleted asynchronously. +lazyfree-lazy-user-flush no + +# On Linux, it is possible to hint the kernel OOM killer on what processes +# should be killed first when out of memory. +# +# Enabling this feature makes Redis actively control the oom_score_adj value +# for all its processes, depending on their role. The default scores will +# attempt to have background child processes killed before all others, and +# replicas killed before masters. +# +# Redis supports these options: +# +# no: Don't make changes to oom-score-adj (default). +# yes: Alias to "relative" see below. +# absolute: Values in oom-score-adj-values are written as is to the kernel. +# relative: Values are used relative to the initial value of oom_score_adj when +# the server starts and are then clamped to a range of -1000 to 1000. +# Because typically the initial value is 0, they will often match the +# absolute values. +oom-score-adj no + +# When oom-score-adj is used, this directive controls the specific values used +# for master, replica and background child processes. Values range -2000 to +# 2000 (higher means more likely to be killed). +# +# Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities) +# can freely increase their value, but not decrease it below its initial +# settings. This means that setting oom-score-adj to "relative" and setting the +# oom-score-adj-values to positive values will always succeed. +oom-score-adj-values 0 200 800 + +# Usually the kernel Transparent Huge Pages control is set to "madvise" or +# or "never" by default (/sys/kernel/mm/transparent_hugepage/enabled), in which +# case this config has no effect. On systems in which it is set to "always", +# redis will attempt to disable it specifically for the redis process in order +# to avoid latency problems specifically with fork(2) and CoW. +# If for some reason you prefer to keep it enabled, you can set this config to +# "no" and the kernel global to "always". +disable-thp yes + +# By default Redis asynchronously dumps the dataset on disk. This mode is +# good enough in many applications, but an issue with the Redis process or +# a power outage may result into a few minutes of writes lost (depending on +# the configured save points). +# +# The Append Only File is an alternative persistence mode that provides +# much better durability. For instance using the default data fsync policy +# (see later in the config file) Redis can lose just one second of writes in a +# dramatic event like a server power outage, or a single write if something +# wrong with the Redis process itself happens, but the operating system is +# still running correctly. +# +# AOF and RDB persistence can be enabled at the same time without problems. +# If the AOF is enabled on startup Redis will load the AOF, that is the file +# with the better durability guarantees. +# +# Please check https://redis.io/topics/persistence for more information. +appendonly no + +# The following time is expressed in microseconds, so 1000000 is equivalent +# to one second. Note that a negative number disables the slow log, while +# a value of zero forces the logging of every command. +slowlog-log-slower-than 10000 + +# There is no limit to this length. Just be aware that it will consume memory. +# You can reclaim memory used by the slow log with SLOWLOG RESET. +slowlog-max-len 128 + +# By default latency monitoring is disabled since it is mostly not needed +# if you don't have latency issues, and collecting data has a performance +# impact, that while very small, can be measured under big load. Latency +# monitoring can easily be enabled at runtime using the command +# "CONFIG SET latency-monitor-threshold " if needed. +latency-monitor-threshold 0 + +# By default all notifications are disabled because most users don't need +# this feature and the feature has some overhead. Note that if you don't +# specify at least one of K or E, no events will be delivered. +notify-keyspace-events "" + +# Hashes are encoded using a memory efficient data structure when they have a +# small number of entries, and the biggest entry does not exceed a given +# threshold. These thresholds can be configured using the following directives. +hash-max-listpack-entries 512 +hash-max-listpack-value 64 + +# Lists are also encoded in a special way to save a lot of space. +# The number of entries allowed per internal list node can be specified +# as a fixed maximum size or a maximum number of elements. +# For a fixed maximum size, use -5 through -1, meaning: +# -5: max size: 64 Kb <-- not recommended for normal workloads +# -4: max size: 32 Kb <-- not recommended +# -3: max size: 16 Kb <-- probably not recommended +# -2: max size: 8 Kb <-- good +# -1: max size: 4 Kb <-- good +# Positive numbers mean store up to _exactly_ that number of elements +# per list node. +# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), +# but if your use case is unique, adjust the settings as necessary. +list-max-listpack-size -2 + +# Lists may also be compressed. +# Compress depth is the number of quicklist ziplist nodes from *each* side of +# the list to *exclude* from compression. The head and tail of the list +# are always uncompressed for fast push/pop operations. Settings are: +# 0: disable all list compression +# 1: depth 1 means "don't start compressing until after 1 node into the list, +# going from either the head or tail" +# So: [head]->node->node->...->node->[tail] +# [head], [tail] will always be uncompressed; inner nodes will compress. +# 2: [head]->[next]->node->node->...->node->[prev]->[tail] +# 2 here means: don't compress head or head->next or tail->prev or tail, +# but compress all nodes between them. +# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail] +# etc. +list-compress-depth 0 + +# Sets have a special encoding when a set is composed +# of just strings that happen to be integers in radix 10 in the range +# of 64 bit signed integers. +# The following configuration setting sets the limit in the size of the +# set in order to use this special memory saving encoding. +set-max-intset-entries 512 + +# Sets containing non-integer values are also encoded using a memory efficient +# data structure when they have a small number of entries, and the biggest entry +# does not exceed a given threshold. These thresholds can be configured using +# the following directives. +set-max-listpack-entries 128 +set-max-listpack-value 64 + +# Similarly to hashes and lists, sorted sets are also specially encoded in +# order to save a lot of space. This encoding is only used when the length and +# elements of a sorted set are below the following limits: +zset-max-listpack-entries 128 +zset-max-listpack-value 64 + +# HyperLogLog sparse representation bytes limit. The limit includes the +# 16 bytes header. When a HyperLogLog using the sparse representation crosses +# this limit, it is converted into the dense representation. +# +# A value greater than 16000 is totally useless, since at that point the +# dense representation is more memory efficient. +# +# The suggested value is ~ 3000 in order to have the benefits of +# the space efficient encoding without slowing down too much PFADD, +# which is O(N) with the sparse encoding. The value can be raised to +# ~ 10000 when CPU is not a concern, but space is, and the data set is +# composed of many HyperLogLogs with cardinality in the 0 - 15000 range. +hll-sparse-max-bytes 3000 + +# Streams macro node max size / items. The stream data structure is a radix +# tree of big nodes that encode multiple items inside. Using this configuration +# it is possible to configure how big a single node can be in bytes, and the +# maximum number of items it may contain before switching to a new node when +# appending new stream entries. If any of the following settings are set to +# zero, the limit is ignored, so for instance it is possible to set just a +# max entries limit by setting max-bytes to 0 and max-entries to the desired +# value. +stream-node-max-bytes 4096 +stream-node-max-entries 100 + +# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in +# order to help rehashing the main Redis hash table (the one mapping top-level +# keys to values). The hash table implementation Redis uses (see dict.c) +# performs a lazy rehashing: the more operation you run into a hash table +# that is rehashing, the more rehashing "steps" are performed, so if the +# server is idle the rehashing is never complete and some more memory is used +# by the hash table. +# +# The default is to use this millisecond 10 times every second in order to +# actively rehash the main dictionaries, freeing memory when possible. +# +# If unsure: +# use "activerehashing no" if you have hard latency requirements and it is +# not a good thing in your environment that Redis can reply from time to time +# to queries with 2 milliseconds delay. +# +# use "activerehashing yes" if you don't have such hard requirements but +# want to free memory asap when possible. +activerehashing yes + +# The client output buffer limits can be used to force disconnection of clients +# that are not reading data from the server fast enough for some reason (a +# common reason is that a Pub/Sub client can't consume messages as fast as the +# publisher can produce them). +# +# Both the hard or the soft limit can be disabled by setting them to zero. +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit replica 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +# Redis calls an internal function to perform many background tasks, like +# closing connections of clients in timeout, purging expired keys that are +# never requested, and so forth. +# +# Not all tasks are performed with the same frequency, but Redis checks for +# tasks to perform according to the specified "hz" value. +# +# By default "hz" is set to 10. Raising the value will use more CPU when +# Redis is idle, but at the same time will make Redis more responsive when +# there are many keys expiring at the same time, and timeouts may be +# handled with more precision. +# +# The range is between 1 and 500, however a value over 100 is usually not +# a good idea. Most users should use the default of 10 and raise this up to +# 100 only in environments where very low latency is required. +hz 10 + +# When dynamic HZ is enabled, the actual configured HZ will be used +# as a baseline, but multiples of the configured HZ value will be actually +# used as needed once more clients are connected. In this way an idle +# instance will use very little CPU time while a busy instance will be +# more responsive. +dynamic-hz yes + +# When a child rewrites the AOF file, if the following option is enabled +# the file will be fsync-ed every 4 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +aof-rewrite-incremental-fsync yes + +# When redis saves RDB file, if the following option is enabled +# the file will be fsync-ed every 4 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +rdb-save-incremental-fsync yes + +# Jemalloc background thread for purging will be enabled by default +jemalloc-bg-thread yes diff --git a/testdata/redis_replica.tdir/redis_replica.conf b/testdata/redis_replica.tdir/redis_replica.conf new file mode 100644 index 000000000..3a558e233 --- /dev/null +++ b/testdata/redis_replica.tdir/redis_replica.conf @@ -0,0 +1,31 @@ +server: + verbosity: 7 + num-threads: 1 + interface: 127.0.0.1 + port: @PORT@ + use-syslog: no + directory: "" + pidfile: "unbound.pid" + chroot: "" + username: "" + module-config: "cachedb iterator" + root-key-sentinel: no + trust-anchor-signaling: no +cachedb: + backend: redis + redis-server-path: @REDIS_SOCKET@ + redis-replica-server-path: @REDIS_REPLICA_SOCKET@ +auth-zone: + name: "redis.com" + for-upstream: yes + for-downstream: no + zonefile: "redis.zone" +remote-control: + control-enable: yes + control-interface: 127.0.0.1 + # control-interface: ::1 + control-port: @CONTROL_PORT@ + server-key-file: "unbound_server.key" + server-cert-file: "unbound_server.pem" + control-key-file: "unbound_control.key" + control-cert-file: "unbound_control.pem" diff --git a/testdata/redis_replica.tdir/redis_replica.dsc b/testdata/redis_replica.tdir/redis_replica.dsc new file mode 100644 index 000000000..03321f11b --- /dev/null +++ b/testdata/redis_replica.tdir/redis_replica.dsc @@ -0,0 +1,16 @@ +BaseName: redis_replica +Version: 1.0 +Description: Test redis replica operation +CreationDate: Fri 01 Mar 15:29:09 CET 2024 +Maintainer: Yorgos Thessalonikefs +Category: +Component: +CmdDepends: +Depends: +Help: +Pre: redis_replica.pre +Post: redis_replica.post +Test: redis_replica.test +AuxFiles: +Passed: +Failure: diff --git a/testdata/redis_replica.tdir/redis_replica.post b/testdata/redis_replica.tdir/redis_replica.post new file mode 100644 index 000000000..afb41f19f --- /dev/null +++ b/testdata/redis_replica.tdir/redis_replica.post @@ -0,0 +1,11 @@ +# #-- redis_replica.post --# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# source the test var file when it's there +[ -f .tpkg.var.test ] && source .tpkg.var.test +# +# do your teardown here +. ../common.sh +kill_pid $REDIS_PID +kill_pid $REDIS_REPLICA_PID +kill_pid $UNBOUND_PID diff --git a/testdata/redis_replica.tdir/redis_replica.pre b/testdata/redis_replica.tdir/redis_replica.pre new file mode 100644 index 000000000..28ccd7b86 --- /dev/null +++ b/testdata/redis_replica.tdir/redis_replica.pre @@ -0,0 +1,46 @@ +# #-- redis_replica.pre--# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# use .tpkg.var.test for in test variable passing +[ -f .tpkg.var.test ] && source .tpkg.var.test + +PRE="../.." +. ../common.sh + +if grep "define USE_REDIS 1" $PRE/config.h; then echo test enabled; else skip_test "test skipped"; fi + +get_random_port 2 +UNBOUND_PORT=$RND_PORT +CONTROL_PORT=$(($RND_PORT + 1)) +echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test +echo "CONTROL_PORT=$CONTROL_PORT" >> .tpkg.var.test + +REDIS_SOCKET=server.sock +REDIS_REPLICA_SOCKET=replica.sock +echo "REDIS_SOCKET=$REDIS_SOCKET" >> .tpkg.var.test +echo "REDIS_REPLICA_SOCKET=$REDIS_REPLICA_SOCKET" >> .tpkg.var.test + +# start redis +sed -e 's/@SOCKET\@/'$REDIS_SOCKET'/' -e 's/@LOGFILE\@/server.log/' < redis.conf > server.conf +redis-server server.conf & +REDIS_PID=$! +echo "REDIS_PID=$REDIS_PID" >> .tpkg.var.test + +# start redis replica +sed -e 's/@SOCKET\@/'$REDIS_REPLICA_SOCKET'/' -e 's/@LOGFILE\@/replica.log/' < redis.conf > replica.conf +redis-server replica.conf & +REDIS_REPLICA_PID=$! +echo "REDIS_REPLICA_PID=$REDIS_REPLICA_PID" >> .tpkg.var.test + +# Copy initial zonefile +cp before.zone redis.zone + +# make config file +sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@REDIS_SOCKET\@/'$REDIS_SOCKET'/' -e 's/@REDIS_REPLICA_SOCKET\@/'$REDIS_REPLICA_SOCKET'/' -e 's/@CONTROL_PORT\@/'$CONTROL_PORT'/' < redis_replica.conf > ub.conf +# start unbound in the background +$PRE/unbound -d -c ub.conf >unbound.log 2>&1 & +UNBOUND_PID=$! +echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test + +cat .tpkg.var.test +wait_unbound_up unbound.log diff --git a/testdata/redis_replica.tdir/redis_replica.test b/testdata/redis_replica.tdir/redis_replica.test new file mode 100644 index 000000000..c3e141dd9 --- /dev/null +++ b/testdata/redis_replica.tdir/redis_replica.test @@ -0,0 +1,80 @@ +# #-- redis_replica.test --# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# use .tpkg.var.test for in test variable passing +[ -f .tpkg.var.test ] && source .tpkg.var.test + +PRE="../.." +# do the test + +# Check number of keys in the db +# $1: socket to connect to +# $2: expected number of keys +redis_cli_check_keys () { + echo "> redis-cli connecting to $1 to check number of keys; expecting $2" + keys=$(redis-cli --no-raw -s $1 keys "*" | grep -vF empty | wc -l) + if test $keys -ne $2 + then + echo "Expected $2 keys, got $keys" + exit 1 + fi + echo "OK" +} + +# Query and check the expected result +# $1: query +# $2: expected answer +expect_answer () { + echo "> dig @127.0.0.1 -p $UNBOUND_PORT $1" + dig @127.0.0.1 -p $UNBOUND_PORT $1 > tmp.answer + if ! grep -F $2 tmp.answer + then + echo "Expected $2 in the answer, got:" + cat tmp.answer + exit 1 + fi + echo "OK" +} + +# Start test + +# check Redis server has no keys +redis_cli_check_keys $REDIS_SOCKET 0 + +# check Redis replica server has no keys +redis_cli_check_keys $REDIS_REPLICA_SOCKET 0 + +# query and check answer +expect_answer redis.com 1.1.1.1 + +# check Redis server has 1 key +redis_cli_check_keys $REDIS_SOCKET 1 + +# check Redis replica server has no keys +redis_cli_check_keys $REDIS_REPLICA_SOCKET 0 + +# change auth zone and reload +cp after.zone redis.zone +echo "$PRE/unbound-control -c ub.conf reload" +$PRE/unbound-control -c ub.conf reload +if test $? -ne 0; then + echo "wrong exit value after success" + exit 1 +fi + +# query and check answer +# we are writing to server but reading from replica; which is not actually +# replicating so the new answer will come through while overwriting the record +# in the server. +expect_answer redis.com 2.2.2.2 + +# check Redis server has 1 key +redis_cli_check_keys $REDIS_SOCKET 1 + +# check Redis replica server has no keys +redis_cli_check_keys $REDIS_REPLICA_SOCKET 0 + +echo "> cat logfiles" +cat unbound.log +echo "> OK" +exit 0 diff --git a/testdata/redis_replica.tdir/unbound_control.key b/testdata/redis_replica.tdir/unbound_control.key new file mode 100644 index 000000000..753a4ef61 --- /dev/null +++ b/testdata/redis_replica.tdir/unbound_control.key @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIG4gIBAAKCAYEAstEp+Pyh8XGrtZ77A4FhYjvbeB3dMa7Q2rGWxobzlA9przhA +1aChAvUtCOAuM+rB6NTNB8YWfZJbQHawyMNpmC77cg6vXLYCGUQHZyAqidN049RJ +F5T7j4N8Vniv17LiRdr0S6swy4PRvEnIPPV43EQHZqC5jVvHsKkhIfmBF/Dj5TXR +ypeawWV/m5jeU6/4HRYMfytBZdO1mPXuWLh0lgbQ4SCbgrOUVD3rniMk1yZIbQOm +vlDHYqekjDb/vOW2KxUQLG04aZMJ1mWfdbwG0CKQkSjISEDZ1l76vhM6mTM0fwXb +IvyFZ9yPPCle1mF5aSlxS2cmGuGVSRQaw8XF9fe3a9ACJJTr33HdSpyaZkKRAUzL +cKqLCl323daKv3NwwAT03Tj4iQM416ASMoiyfFa/2GWTKQVjddu8Crar7tGaf5xr +lig4DBmrBvdYA3njy72/RD71hLwmlRoCGU7dRuDr9O6KASUm1Ri91ONZ/qdjMvov +15l2vj4GV+KXR00dAgMBAAECggGAHepIL1N0dEQkCdpy+/8lH54L9WhpnOo2HqAf +LU9eaKK7d4jdr9+TkD8cLaPzltPrZNxVALvu/0sA4SP6J1wpyj/x6P7z73qzly5+ +Xo5PD4fEwmi9YaiW/UduAblnEZrnp/AddptJKoL/D5T4XtpiQddPtael4zQ7kB57 +YIexRSQTvEDovA/o3/nvA0TrzOxfgd4ycQP3iOWGN/TMzyLsvjydrUwbOB567iz9 +whL3Etdgvnwh5Sz2blbFfH+nAR8ctvFFz+osPvuIVR21VMEI6wm7kTpSNnQ6sh/c +lrLb/bTADn4g7z/LpIZJ+MrLvyEcoqValrLYeFBhM9CV8woPxvkO2P3pU47HVGax +tC7GV6a/kt5RoKFd/TNdiA3OC7NGZtaeXv9VkPf4fVwBtSO9d5ZZXTGEynDD/rUQ +U4KFJe6OD23APjse08HiiKqTPhsOneOONU67iqoaTdIkT2R4EdlkVEDpXVtWb+G9 +Q+IqYzVljlzuyHrhWXLJw/FMa2aBAoHBAOnZbi4gGpH+P6886WDWVgIlTccuXoyc +Mg9QQYk9UDeXxL0AizR5bZy49Sduegz9vkHpAiZARQsUnizHjZ8YlRcrmn4t6tx3 +ahTIKAjdprnxJfYINM580j8CGbXvX5LhIlm3O267D0Op+co3+7Ujy+cjsIuFQrP+ +1MqMgXSeBjzC1APivmps7HeFE+4w0k2PfN5wSMDNCzLo99PZuUG5XZ93OVOS5dpN +b+WskdcD8NOoJy/X/5A08veEI/jYO/DyqQKBwQDDwUQCOWf41ecvJLtBHKmEnHDz +ftzHino9DRKG8a9XaN4rmetnoWEaM2vHGX3pf3mwH+dAe8vJdAQueDhBKYeEpm6C +TYNOpou1+Zs5s99BilCTNYo8fkMOAyqwRwmz9zgHS6QxXuPwsghKefLJGt6o6RFF +tfWVTfLlYJ+I3GQe3ySsk3wjVz4oUTKiyiq5+KzD+HhEkS7u+RQ7Z0ZI2xd2cF8Y +aN2hjKDpcOiFf3CDoqka5D1qMNLgIHO52AHww1UCgcA1h7o7AMpURRka6hyaODY0 +A4oMYEbwdQjYjIyT998W+rzkbu1us6UtzQEBZ760npkgyU/epbOoV63lnkCC/MOU +LD0PST+L/CHiY/cWIHb79YG1EifUZKpUFg0Aoq0EGFkepF0MefGCkbRGYA5UZr9U +R80wAu9D+L+JJiS0J0BSRF74DL196zUuHt5zFeXuLzxsRtPAnq9DliS08BACRYZy +7H3I7cWD9Vn5/0jbKWHFcaaWwyETR6uekTcSzZzbCRECgcBeoE3/xUA9SSk34Mmj +7/cB4522Ft0imA3+9RK/qJTZ7Bd5fC4PKjOGNtUiqW/0L2rjeIiQ40bfWvWqgPKw +jSK1PL6uvkl6+4cNsFsYyZpiVDoe7wKju2UuoNlB3RUTqa2r2STFuNj2wRjA57I1 +BIgdnox65jqQsd14g/yaa+75/WP9CE45xzKEyrtvdcqxm0Pod3OrsYK+gikFjiar +kT0GQ8u0QPzh2tjt/2ZnIfOBrl+QYERP0MofDZDjhUdq2wECgcB0Lu841+yP5cdR +qbJhXO4zJNh7oWNcJlOuQp3ZMNFrA1oHpe9pmLukiROOy01k9WxIMQDzU5GSqRv3 +VLkYOIcbhJ3kClKAcM3j95SkKbU2H5/RENb3Ck52xtl4pNU1x/3PnVFZfDVuuHO9 +MZ9YBcIeK98MyP2jr5JtFKnOyPE7xKq0IHIhXadpbc2wjje5FtZ1cUtMyEECCXNa +C1TpXebHGyXGpY9WdWXhjdE/1jPvfS+uO5WyuDpYPr339gsdq1g= +-----END RSA PRIVATE KEY----- diff --git a/testdata/redis_replica.tdir/unbound_control.pem b/testdata/redis_replica.tdir/unbound_control.pem new file mode 100644 index 000000000..a1edf7017 --- /dev/null +++ b/testdata/redis_replica.tdir/unbound_control.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDszCCAhsCFGD5193whHQ2bVdzbaQfdf1gc4SkMA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAMMB3VuYm91bmQwHhcNMjAwNzA4MTMzMjMwWhcNNDAwMzI1MTMzMjMw +WjAaMRgwFgYDVQQDDA91bmJvdW5kLWNvbnRyb2wwggGiMA0GCSqGSIb3DQEBAQUA +A4IBjwAwggGKAoIBgQCy0Sn4/KHxcau1nvsDgWFiO9t4Hd0xrtDasZbGhvOUD2mv +OEDVoKEC9S0I4C4z6sHo1M0HxhZ9kltAdrDIw2mYLvtyDq9ctgIZRAdnICqJ03Tj +1EkXlPuPg3xWeK/XsuJF2vRLqzDLg9G8Scg89XjcRAdmoLmNW8ewqSEh+YEX8OPl +NdHKl5rBZX+bmN5Tr/gdFgx/K0Fl07WY9e5YuHSWBtDhIJuCs5RUPeueIyTXJkht +A6a+UMdip6SMNv+85bYrFRAsbThpkwnWZZ91vAbQIpCRKMhIQNnWXvq+EzqZMzR/ +Bdsi/IVn3I88KV7WYXlpKXFLZyYa4ZVJFBrDxcX197dr0AIklOvfcd1KnJpmQpEB +TMtwqosKXfbd1oq/c3DABPTdOPiJAzjXoBIyiLJ8Vr/YZZMpBWN127wKtqvu0Zp/ +nGuWKDgMGasG91gDeePLvb9EPvWEvCaVGgIZTt1G4Ov07ooBJSbVGL3U41n+p2My ++i/XmXa+PgZX4pdHTR0CAwEAATANBgkqhkiG9w0BAQsFAAOCAYEAd++Wen6l8Ifj +4h3p/y16PhSsWJWuJ4wdNYy3/GM84S26wGjzlEEwiW76HpH6VJzPOiBAeWnFKE83 +hFyetEIxgJeIPbcs9ZP/Uoh8GZH9tRISBSN9Hgk2Slr9llo4t1H0g/XTgA5HqMQU +9YydlBh43G7Vw3FVwh09OM6poNOGQKNc/tq2/QdKeUMtyBbLWpRmjH5XcCT35fbn +ZiVOUldqSHD4kKrFO4nJYXZyipRbcXybsLiX9GP0GLemc3IgIvOXyJ2RPp06o/SJ +pzlMlkcAfLJaSuEW57xRakhuNK7m051TKKzJzIEX+NFYOVdafFHS8VwGrYsdrFvD +72tMfu+Fu55y3awdWWGc6YlaGogZiuMnJkvQphwgn+5qE/7CGEckoKEsH601rqIZ +muaIc85+nEcHJeijd/ZlBN9zeltjFoMuqTUENgmv8+tUAdVm/UMY9Vjme6b43ydP +uv6DS02+k9z8toxXworLiPr94BGaiGV1NxgwZKLZigYJt/Fi2Qte +-----END CERTIFICATE----- diff --git a/testdata/redis_replica.tdir/unbound_server.key b/testdata/redis_replica.tdir/unbound_server.key new file mode 100644 index 000000000..370a7bbb2 --- /dev/null +++ b/testdata/redis_replica.tdir/unbound_server.key @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIG5AIBAAKCAYEAvjSVSN2QMXudpzukdLCqgg/IOhCX8KYkD0FFFfWcQjgKq5wI +0x41iG32a6wbGanre4IX7VxaSPu9kkHfnGgynCk5nwDRedE/FLFhAU78PoT0+Nqq +GRS7XVQ24vLmIz9Hqc2Ozx1um1BXBTmIT0UfN2e22I0LWQ6a3seZlEDRj45gnk7Z +uh9MDgotaBdm+v1JAbupSf6Zis4VEH3JNdvVGE3O1DHEIeuuz/3BDhpf6WBDH+8K +WaBe1ca4TZHr9ThL2gEMEfAQl0wXDwRWRoi3NjNMH+mw0L1rjwThI5GXqNIee7o5 +FzUReSXZuTdFMyGe3Owcx+XoYnwi6cplSNoGsDBu4B9bKKglR9YleJVw4L4Xi8xP +q6O9UPj4+nypHk/DOoC7DIM3ufN0yxPBsFo5TVowxfhdjZXJbbftd2TZv7AH8+XL +A5UoZgRzXgzECelXSCTBFlMTnT48LfA9pMLydyjAz2UdPHs5Iv+TK5nnI+aJoeaP +7kFZSngxdy1+A/bNAgMBAAECggGBALpTOIqQwVg4CFBylL/a8K1IWJTI/I65sklf +XxYL7G7SB2HlEJ//z+E+F0+S4Vlao1vyLQ5QkgE82pAUB8FoMWvY1qF0Y8A5wtm6 +iZSGk4OLK488ZbT8Ii9i+AGKgPe2XbVxsJwj8N4k7Zooqec9hz73Up8ATEWJkRz7 +2u7oMGG4z91E0PULA64dOi3l/vOQe5w/Aa+CwVbAWtI05o7kMvQEBMDJn6C7CByo +MB5op9wueJMnz7PM7hns+U7Dy6oE4ljuolJUy51bDzFWwoM54cRoQqLFNHd8JVQj +WxldCkbfF43iyprlsEcUrTyUjtdA+ZeiG39vg/mtdmgNpGmdupHJZQvSuG8IcVlz +O+eMSeQS1QXPD6Ik8UK4SU0h+zOl8xIWtRrsxQuh4fnTN40udm/YUWl/6gOebsBI +IrVLlKGqJSfB3tMjpCRqdTzJ0dA9keVpkqm2ugZkxEf1+/efq/rFIQ2pUBLCqNTN +qpNqruK8y8FphP30I2uI4Ej2UIB8AQKBwQDd2Yptj2FyDyaXCycsyde0wYkNyzGU +dRnzdibfHnMZwjgTjwAwgIUBVIS8H0/z7ZJQKN7osJfddMrtjJtYYUk9g/dCpHXs +bNh2QSoWah3FdzNGuWd0iRf9+LFxhjAAMo/FS8zFJAJKrFsBdCGTfFUMdsLC0bjr +YjiWBuvV72uKf8XIZX5KIZruKdWBBcWukcb21R1UDyFYyXRBsly5XHaIYKZql3km +7pV7MKWO0IYgHbHIqGUqPQlzZ/lkunS1jKECgcEA23wHffD6Ou9/x3okPx2AWpTr +gh8rgqbyo6hQkBW5Y90Wz824cqaYebZDaBR/xlVx/YwjKkohv8Bde2lpH/ZxRZ1Z +5Sk2s6GJ/vU0L9RsJZgCgj4L6Coal1NMxuZtCXAlnOpiCdxSZgfqbshbTVz30KsG +ZJG361Cua1ScdAHxlZBxT52/1Sm0zRC2hnxL7h4qo7Idmtzs40LAJvYOKekR0pPN +oWeJfra7vgx/jVNvMFWoOoSLpidVO4g+ot4ery6tAoHAdW3rCic1C2zdnmH28Iw+ +s50l8Lk3mz+I5wgJd1zkzCO0DxZIoWPGA3g7cmCYr6N3KRsZMs4W9NAXgjpFGDkW +zYsG3K21BdpvkdjYcFjnPVjlOXB2RIc0vehf9Jl02wXoeCSxVUDEPcaRvWk9RJYx +ZpGOchUU7vNkxHURbIJ4yCzuAi9G8/Jp0dsu+kaV5tufF5SjG5WOrzKjaQsCbdN1 +oqaWMCHRrTvov/Z2C+xwsptFOdN5CSyZzg6hQiI4GMlBAoHAXyb6KINcOEi0YMp3 +BFXJ23tMTnEs78tozcKeipigcsbaqORK3omS+NEnj+uzKUzJyl4CsMbKstK2tFYS +mSTCHqgE3PBtIpsZtEqhgUraR8IK9GPpzZDTTl9ynZgwFTNlWw3RyuyVXF56J+T8 +kCGJ3hEHCHqT/ZRQyX85BKIDFhA0z4tYKxWVqIFiYBNq56R0X9tMMmMs36mEnF93 +7Ht6mowxTZQRa7nU0qOgeKh/P7ki4Zus3y+WJ+T9IqahLtlRAoHBAIhqMrcxSAB8 +RpB9jukJlAnidw2jCMPgrFE8tP0khhVvGrXMldxAUsMKntDIo8dGCnG1KTcWDI0O +jepvSPHSsxVLFugL79h0eVIS5z4huW48i9xgU8VlHdgAcgEPIAOFcOw2BCu/s0Vp +O+MM/EyUOdo3NsibB3qc/GJI6iNBYS7AljYEVo6rXo5V/MZvZUF4vClen6Obzsre +MTTb+4sJjfqleWuvr1XNMeu2mBfXBQkWGZP1byBK0MvD/aQ2PWq92A== +-----END RSA PRIVATE KEY----- diff --git a/testdata/redis_replica.tdir/unbound_server.pem b/testdata/redis_replica.tdir/unbound_server.pem new file mode 100644 index 000000000..986807310 --- /dev/null +++ b/testdata/redis_replica.tdir/unbound_server.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDqzCCAhMCFBHWXeQ6ZIa9QcQbXLFfC6tj+KA+MA0GCSqGSIb3DQEBCwUAMBIx +EDAOBgNVBAMMB3VuYm91bmQwHhcNMjAwNzA4MTMzMjI5WhcNNDAwMzI1MTMzMjI5 +WjASMRAwDgYDVQQDDAd1bmJvdW5kMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB +igKCAYEAvjSVSN2QMXudpzukdLCqgg/IOhCX8KYkD0FFFfWcQjgKq5wI0x41iG32 +a6wbGanre4IX7VxaSPu9kkHfnGgynCk5nwDRedE/FLFhAU78PoT0+NqqGRS7XVQ2 +4vLmIz9Hqc2Ozx1um1BXBTmIT0UfN2e22I0LWQ6a3seZlEDRj45gnk7Zuh9MDgot +aBdm+v1JAbupSf6Zis4VEH3JNdvVGE3O1DHEIeuuz/3BDhpf6WBDH+8KWaBe1ca4 +TZHr9ThL2gEMEfAQl0wXDwRWRoi3NjNMH+mw0L1rjwThI5GXqNIee7o5FzUReSXZ +uTdFMyGe3Owcx+XoYnwi6cplSNoGsDBu4B9bKKglR9YleJVw4L4Xi8xPq6O9UPj4 ++nypHk/DOoC7DIM3ufN0yxPBsFo5TVowxfhdjZXJbbftd2TZv7AH8+XLA5UoZgRz +XgzECelXSCTBFlMTnT48LfA9pMLydyjAz2UdPHs5Iv+TK5nnI+aJoeaP7kFZSngx +dy1+A/bNAgMBAAEwDQYJKoZIhvcNAQELBQADggGBABunf93MKaCUHiZgnoOTinsW +84/EgInrgtKzAyH+BhnKkJOhhR0kkIAx5d9BpDlaSiRTACFon9moWCgDIIsK/Ar7 +JE0Kln9cV//wiiNoFU0O4mnzyGUIMvlaEX6QHMJJQYvL05+w/3AAcf5XmMJtR5ca +fJ8FqvGC34b2WxX9lTQoyT52sRt+1KnQikiMEnEyAdKktMG+MwKsFDdOwDXyZhZg +XZhRrfX3/NVJolqB6EahjWIGXDeKuSSKZVtCyib6LskyeMzN5lcRfvubKDdlqFVF +qlD7rHBsKhQUWK/IO64mGf7y/de+CgHtED5vDvr/p2uj/9sABATfbrOQR3W/Of25 +sLBj4OEfrJ7lX8hQgFaxkMI3x6VFT3W8dTCp7xnQgb6bgROWB5fNEZ9jk/gjSRmD +yIU+r0UbKe5kBk/CmZVFXL2TyJ92V5NYEQh8V4DGy19qZ6u/XKYyNJL4ocs35GGe +CA8SBuyrmdhx38h1RHErR2Skzadi1S7MwGf1y431fQ== +-----END CERTIFICATE----- diff --git a/util/config_file.c b/util/config_file.c index f763cea32..a24067060 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -395,14 +395,22 @@ config_create(void) cfg->cachedb_check_when_serve_expired = 1; #ifdef USE_REDIS if(!(cfg->redis_server_host = strdup("127.0.0.1"))) goto error_exit; + if(!(cfg->redis_replica_server_host = strdup(""))) goto error_exit; cfg->redis_server_path = NULL; + cfg->redis_replica_server_path = NULL; cfg->redis_server_password = NULL; + cfg->redis_replica_server_password = NULL; cfg->redis_timeout = 100; + cfg->redis_replica_timeout = 100; cfg->redis_command_timeout = 0; + cfg->redis_replica_command_timeout = 0; cfg->redis_connect_timeout = 0; + cfg->redis_replica_connect_timeout = 0; cfg->redis_server_port = 6379; + cfg->redis_replica_server_port = 6379; cfg->redis_expire_records = 0; cfg->redis_logical_db = 0; + cfg->redis_replica_logical_db = 0; #endif /* USE_REDIS */ #endif /* USE_CACHEDB */ #ifdef USE_IPSET @@ -1388,14 +1396,22 @@ config_get_option(struct config_file* cfg, const char* opt, else O_YNO(opt, "cachedb-check-when-serve-expired", cachedb_check_when_serve_expired) #ifdef USE_REDIS else O_STR(opt, "redis-server-host", redis_server_host) + else O_STR(opt, "redis-replica-server-host", redis_replica_server_host) else O_DEC(opt, "redis-server-port", redis_server_port) + else O_DEC(opt, "redis-replica-server-port", redis_replica_server_port) else O_STR(opt, "redis-server-path", redis_server_path) + else O_STR(opt, "redis-replica-server-path", redis_replica_server_path) else O_STR(opt, "redis-server-password", redis_server_password) + else O_STR(opt, "redis-replica-server-password", redis_replica_server_password) else O_DEC(opt, "redis-timeout", redis_timeout) + else O_DEC(opt, "redis-replica-timeout", redis_replica_timeout) else O_DEC(opt, "redis-command-timeout", redis_command_timeout) + else O_DEC(opt, "redis-replica-command-timeout", redis_replica_command_timeout) else O_DEC(opt, "redis-connect-timeout", redis_connect_timeout) + else O_DEC(opt, "redis-replica-connect-timeout", redis_replica_connect_timeout) else O_YNO(opt, "redis-expire-records", redis_expire_records) else O_DEC(opt, "redis-logical-db", redis_logical_db) + else O_DEC(opt, "redis-replica-logical-db", redis_replica_logical_db) #endif /* USE_REDIS */ #endif /* USE_CACHEDB */ #ifdef USE_IPSET @@ -1775,8 +1791,11 @@ config_delete(struct config_file* cfg) free(cfg->cachedb_secret); #ifdef USE_REDIS free(cfg->redis_server_host); + free(cfg->redis_replica_server_host); free(cfg->redis_server_path); + free(cfg->redis_replica_server_path); free(cfg->redis_server_password); + free(cfg->redis_replica_server_password); #endif /* USE_REDIS */ #endif /* USE_CACHEDB */ #ifdef USE_IPSET diff --git a/util/config_file.h b/util/config_file.h index a1dec45b0..a5d73f4c6 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -741,22 +741,30 @@ struct config_file { #ifdef USE_REDIS /** redis server's IP address or host name */ char* redis_server_host; + char* redis_replica_server_host; /** redis server's TCP port */ int redis_server_port; + int redis_replica_server_port; /** redis server's unix path. Or "", NULL if unused */ char* redis_server_path; + char* redis_replica_server_path; /** redis server's AUTH password. Or "", NULL if unused */ char* redis_server_password; + char* redis_replica_server_password; /** timeout (in ms) for communication with the redis server */ int redis_timeout; + int redis_replica_timeout; /** timeout (in ms) for redis commands */ int redis_command_timeout; + int redis_replica_command_timeout; /** timeout (in ms) for redis connection set up */ int redis_connect_timeout; + int redis_replica_connect_timeout; /** set timeout on redis records based on DNS response ttl */ int redis_expire_records; /** set the redis logical database upon connection */ int redis_logical_db; + int redis_replica_logical_db; #endif #endif /** Downstream DNS Cookies */ diff --git a/util/configlexer.lex b/util/configlexer.lex index f0d470227..1b9eaa35b 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -572,15 +572,23 @@ backend{COLON} { YDVAR(1, VAR_CACHEDB_BACKEND) } secret-seed{COLON} { YDVAR(1, VAR_CACHEDB_SECRETSEED) } cachedb-no-store{COLON} { YDVAR(1, VAR_CACHEDB_NO_STORE) } cachedb-check-when-serve-expired{COLON} { YDVAR(1, VAR_CACHEDB_CHECK_WHEN_SERVE_EXPIRED) } -redis-server-host{COLON} { YDVAR(1, VAR_CACHEDB_REDISHOST) } -redis-server-port{COLON} { YDVAR(1, VAR_CACHEDB_REDISPORT) } -redis-server-path{COLON} { YDVAR(1, VAR_CACHEDB_REDISPATH) } -redis-server-password{COLON} { YDVAR(1, VAR_CACHEDB_REDISPASSWORD) } -redis-timeout{COLON} { YDVAR(1, VAR_CACHEDB_REDISTIMEOUT) } +redis-server-host{COLON} { YDVAR(1, VAR_CACHEDB_REDISHOST) } +redis-replica-server-host{COLON} { YDVAR(1, VAR_CACHEDB_REDISREPLICAHOST) } +redis-server-port{COLON} { YDVAR(1, VAR_CACHEDB_REDISPORT) } +redis-replica-server-port{COLON} { YDVAR(1, VAR_CACHEDB_REDISREPLICAPORT) } +redis-server-path{COLON} { YDVAR(1, VAR_CACHEDB_REDISPATH) } +redis-replica-server-path{COLON} { YDVAR(1, VAR_CACHEDB_REDISREPLICAPATH) } +redis-server-password{COLON} { YDVAR(1, VAR_CACHEDB_REDISPASSWORD) } +redis-replica-server-password{COLON} { YDVAR(1, VAR_CACHEDB_REDISREPLICAPASSWORD) } +redis-timeout{COLON} { YDVAR(1, VAR_CACHEDB_REDISTIMEOUT) } +redis-replica-timeout{COLON} { YDVAR(1, VAR_CACHEDB_REDISREPLICATIMEOUT) } redis-command-timeout{COLON} { YDVAR(1, VAR_CACHEDB_REDISCOMMANDTIMEOUT) } +redis-replica-command-timeout{COLON} { YDVAR(1, VAR_CACHEDB_REDISREPLICACOMMANDTIMEOUT) } redis-connect-timeout{COLON} { YDVAR(1, VAR_CACHEDB_REDISCONNECTTIMEOUT) } +redis-replica-connect-timeout{COLON} { YDVAR(1, VAR_CACHEDB_REDISREPLICACONNECTTIMEOUT) } redis-expire-records{COLON} { YDVAR(1, VAR_CACHEDB_REDISEXPIRERECORDS) } -redis-logical-db{COLON} { YDVAR(1, VAR_CACHEDB_REDISLOGICALDB) } +redis-logical-db{COLON} { YDVAR(1, VAR_CACHEDB_REDISLOGICALDB) } +redis-replica-logical-db{COLON} { YDVAR(1, VAR_CACHEDB_REDISREPLICALOGICALDB) } ipset{COLON} { YDVAR(0, VAR_IPSET) } name-v4{COLON} { YDVAR(1, VAR_IPSET_NAME_V4) } name-v6{COLON} { YDVAR(1, VAR_IPSET_NAME_V6) } diff --git a/util/configparser.y b/util/configparser.y index c10a5f475..af47b0eb7 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -179,10 +179,15 @@ extern struct config_parser_state* cfg_parser; %token VAR_IPSECMOD_ENABLED VAR_IPSECMOD_HOOK VAR_IPSECMOD_IGNORE_BOGUS %token VAR_IPSECMOD_MAX_TTL VAR_IPSECMOD_WHITELIST VAR_IPSECMOD_STRICT %token VAR_CACHEDB VAR_CACHEDB_BACKEND VAR_CACHEDB_SECRETSEED -%token VAR_CACHEDB_REDISHOST VAR_CACHEDB_REDISPORT VAR_CACHEDB_REDISTIMEOUT -%token VAR_CACHEDB_REDISEXPIRERECORDS VAR_CACHEDB_REDISPATH VAR_CACHEDB_REDISPASSWORD -%token VAR_CACHEDB_REDISLOGICALDB -%token VAR_CACHEDB_REDISCOMMANDTIMEOUT VAR_CACHEDB_REDISCONNECTTIMEOUT +%token VAR_CACHEDB_REDISHOST VAR_CACHEDB_REDISREPLICAHOST +%token VAR_CACHEDB_REDISPORT VAR_CACHEDB_REDISREPLICAPORT +%token VAR_CACHEDB_REDISTIMEOUT VAR_CACHEDB_REDISREPLICATIMEOUT +%token VAR_CACHEDB_REDISEXPIRERECORDS +%token VAR_CACHEDB_REDISPATH VAR_CACHEDB_REDISREPLICAPATH +%token VAR_CACHEDB_REDISPASSWORD VAR_CACHEDB_REDISREPLICAPASSWORD +%token VAR_CACHEDB_REDISLOGICALDB VAR_CACHEDB_REDISREPLICALOGICALDB +%token VAR_CACHEDB_REDISCOMMANDTIMEOUT VAR_CACHEDB_REDISREPLICACOMMANDTIMEOUT +%token VAR_CACHEDB_REDISCONNECTTIMEOUT VAR_CACHEDB_REDISREPLICACONNECTTIMEOUT %token VAR_UDP_UPSTREAM_WITHOUT_DOWNSTREAM VAR_FOR_UPSTREAM %token VAR_AUTH_ZONE VAR_ZONEFILE VAR_MASTER VAR_URL VAR_FOR_DOWNSTREAM %token VAR_FALLBACK_ENABLED VAR_TLS_ADDITIONAL_PORT VAR_LOW_RTT VAR_LOW_RTT_PERMIL @@ -3868,10 +3873,16 @@ cachedbstart: VAR_CACHEDB contents_cachedb: contents_cachedb content_cachedb | ; content_cachedb: cachedb_backend_name | cachedb_secret_seed | - redis_server_host | redis_server_port | redis_timeout | - redis_expire_records | redis_server_path | redis_server_password | - cachedb_no_store | redis_logical_db | cachedb_check_when_serve_expired | - redis_command_timeout | redis_connect_timeout + redis_server_host | redis_replica_server_host | + redis_server_port | redis_replica_server_port | + redis_timeout | redis_replica_timeout | + redis_command_timeout | redis_replica_command_timeout | + redis_connect_timeout | redis_replica_connect_timeout | + redis_server_path | redis_replica_server_path | + redis_server_password | redis_replica_server_password | + redis_logical_db | redis_replica_logical_db | + cachedb_no_store | redis_expire_records | + cachedb_check_when_serve_expired ; cachedb_backend_name: VAR_CACHEDB_BACKEND STRING_ARG { @@ -3935,6 +3946,18 @@ redis_server_host: VAR_CACHEDB_REDISHOST STRING_ARG #endif } ; +redis_replica_server_host: VAR_CACHEDB_REDISREPLICAHOST STRING_ARG + { + #if defined(USE_CACHEDB) && defined(USE_REDIS) + OUTYY(("P(redis_replica_server_host:%s)\n", $2)); + free(cfg_parser->cfg->redis_replica_server_host); + cfg_parser->cfg->redis_replica_server_host = $2; + #else + OUTYY(("P(Compiled without cachedb or redis, ignoring)\n")); + free($2); + #endif + } + ; redis_server_port: VAR_CACHEDB_REDISPORT STRING_ARG { #if defined(USE_CACHEDB) && defined(USE_REDIS) @@ -3950,6 +3973,21 @@ redis_server_port: VAR_CACHEDB_REDISPORT STRING_ARG free($2); } ; +redis_replica_server_port: VAR_CACHEDB_REDISREPLICAPORT STRING_ARG + { + #if defined(USE_CACHEDB) && defined(USE_REDIS) + int port; + OUTYY(("P(redis_replica_server_port:%s)\n", $2)); + port = atoi($2); + if(port == 0 || port < 0 || port > 65535) + yyerror("valid redis server port number expected"); + else cfg_parser->cfg->redis_replica_server_port = port; + #else + OUTYY(("P(Compiled without cachedb or redis, ignoring)\n")); + #endif + free($2); + } + ; redis_server_path: VAR_CACHEDB_REDISPATH STRING_ARG { #if defined(USE_CACHEDB) && defined(USE_REDIS) @@ -3962,6 +4000,18 @@ redis_server_path: VAR_CACHEDB_REDISPATH STRING_ARG #endif } ; +redis_replica_server_path: VAR_CACHEDB_REDISREPLICAPATH STRING_ARG + { + #if defined(USE_CACHEDB) && defined(USE_REDIS) + OUTYY(("P(redis_replica_server_path:%s)\n", $2)); + free(cfg_parser->cfg->redis_replica_server_path); + cfg_parser->cfg->redis_replica_server_path = $2; + #else + OUTYY(("P(Compiled without cachedb or redis, ignoring)\n")); + free($2); + #endif + } + ; redis_server_password: VAR_CACHEDB_REDISPASSWORD STRING_ARG { #if defined(USE_CACHEDB) && defined(USE_REDIS) @@ -3974,6 +4024,18 @@ redis_server_password: VAR_CACHEDB_REDISPASSWORD STRING_ARG #endif } ; +redis_replica_server_password: VAR_CACHEDB_REDISREPLICAPASSWORD STRING_ARG + { + #if defined(USE_CACHEDB) && defined(USE_REDIS) + OUTYY(("P(redis_replica_server_password:%s)\n", $2)); + free(cfg_parser->cfg->redis_replica_server_password); + cfg_parser->cfg->redis_replica_server_password = $2; + #else + OUTYY(("P(Compiled without cachedb or redis, ignoring)\n")); + free($2); + #endif + } + ; redis_timeout: VAR_CACHEDB_REDISTIMEOUT STRING_ARG { #if defined(USE_CACHEDB) && defined(USE_REDIS) @@ -3987,6 +4049,19 @@ redis_timeout: VAR_CACHEDB_REDISTIMEOUT STRING_ARG free($2); } ; +redis_replica_timeout: VAR_CACHEDB_REDISREPLICATIMEOUT STRING_ARG + { + #if defined(USE_CACHEDB) && defined(USE_REDIS) + OUTYY(("P(redis_replica_timeout:%s)\n", $2)); + if(atoi($2) == 0) + yyerror("redis timeout value expected"); + else cfg_parser->cfg->redis_replica_timeout = atoi($2); + #else + OUTYY(("P(Compiled without cachedb or redis, ignoring)\n")); + #endif + free($2); + } + ; redis_command_timeout: VAR_CACHEDB_REDISCOMMANDTIMEOUT STRING_ARG { #if defined(USE_CACHEDB) && defined(USE_REDIS) @@ -4000,6 +4075,19 @@ redis_command_timeout: VAR_CACHEDB_REDISCOMMANDTIMEOUT STRING_ARG free($2); } ; +redis_replica_command_timeout: VAR_CACHEDB_REDISREPLICACOMMANDTIMEOUT STRING_ARG + { + #if defined(USE_CACHEDB) && defined(USE_REDIS) + OUTYY(("P(redis_replica_command_timeout:%s)\n", $2)); + if(atoi($2) == 0 && strcmp($2, "0") != 0) + yyerror("redis command timeout value expected"); + else cfg_parser->cfg->redis_replica_command_timeout = atoi($2); + #else + OUTYY(("P(Compiled without cachedb or redis, ignoring)\n")); + #endif + free($2); + } + ; redis_connect_timeout: VAR_CACHEDB_REDISCONNECTTIMEOUT STRING_ARG { #if defined(USE_CACHEDB) && defined(USE_REDIS) @@ -4013,6 +4101,19 @@ redis_connect_timeout: VAR_CACHEDB_REDISCONNECTTIMEOUT STRING_ARG free($2); } ; +redis_replica_connect_timeout: VAR_CACHEDB_REDISREPLICACONNECTTIMEOUT STRING_ARG + { + #if defined(USE_CACHEDB) && defined(USE_REDIS) + OUTYY(("P(redis_replica_connect_timeout:%s)\n", $2)); + if(atoi($2) == 0 && strcmp($2, "0") != 0) + yyerror("redis connect timeout value expected"); + else cfg_parser->cfg->redis_replica_connect_timeout = atoi($2); + #else + OUTYY(("P(Compiled without cachedb or redis, ignoring)\n")); + #endif + free($2); + } + ; redis_expire_records: VAR_CACHEDB_REDISEXPIRERECORDS STRING_ARG { #if defined(USE_CACHEDB) && defined(USE_REDIS) @@ -4041,6 +4142,21 @@ redis_logical_db: VAR_CACHEDB_REDISLOGICALDB STRING_ARG free($2); } ; +redis_replica_logical_db: VAR_CACHEDB_REDISREPLICALOGICALDB STRING_ARG + { + #if defined(USE_CACHEDB) && defined(USE_REDIS) + int db; + OUTYY(("P(redis_replica_logical_db:%s)\n", $2)); + db = atoi($2); + if((db == 0 && strcmp($2, "0") != 0) || db < 0) + yyerror("valid redis logical database index expected"); + else cfg_parser->cfg->redis_replica_logical_db = db; + #else + OUTYY(("P(Compiled without cachedb or redis, ignoring)\n")); + #endif + free($2); + } + ; server_tcp_connection_limit: VAR_TCP_CONNECTION_LIMIT STRING_ARG STRING_ARG { OUTYY(("P(server_tcp_connection_limit:%s %s)\n", $2, $3)); From 0e5d26807d1552ae4ae0b1ea0100349aa8bdac38 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 4 Apr 2025 10:30:22 +0200 Subject: [PATCH 090/218] - Fix nettle compile for warnings and ticket keys. --- doc/Changelog | 1 + util/net_help.c | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 6576c3455..301d53f8a 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,7 @@ 4 April 2025: Wouter - Fix mesh_copy_client_info to omit null contents from copy. - Fix comment name in the rpz nsdname test. + - Fix nettle compile for warnings and ticket keys. 3 April 2025: Wouter - Fix #1263: Exempt loopback addresses from wait-limit. diff --git a/util/net_help.c b/util/net_help.c index c57228eee..8eca6b757 100644 --- a/util/net_help.c +++ b/util/net_help.c @@ -92,11 +92,13 @@ int RRSET_ROUNDROBIN = 1; /** log tag queries with name instead of 'info' for filtering */ int LOG_TAG_QUERYREPLY = 0; +#ifdef HAVE_SSL static struct tls_session_ticket_key { unsigned char *key_name; unsigned char *aes_key; unsigned char *hmac_key; } *ticket_keys; +#endif /* HAVE_SSL */ #ifdef HAVE_SSL /** @@ -1198,6 +1200,7 @@ static int doh_alpn_select_cb(SSL* ATTR_UNUSED(ssl), const unsigned char** out, } #endif +#ifdef HAVE_SSL /* setup the callback for ticket keys */ static int setup_ticket_keys_cb(void* sslctx) @@ -1213,7 +1216,7 @@ setup_ticket_keys_cb(void* sslctx) # endif return 1; } - +#endif /* HAVE_SSL */ int listen_sslctx_setup(void* ctxt) @@ -1417,7 +1420,7 @@ void* listen_sslctx_create(const char* key, const char* pem, #else (void)key; (void)pem; (void)verifypem; (void)tls_ciphers; (void)tls_ciphersuites; - (void)tls_session_ticket_keys; + (void)set_ticket_keys_cb; (void)is_dot; (void)is_doh; return NULL; #endif /* HAVE_SSL */ } @@ -1940,6 +1943,7 @@ int tls_session_ticket_key_cb(SSL *ATTR_UNUSED(sslctx), unsigned char* key_name, } #endif /* HAVE_SSL */ +#ifdef HAVE_SSL void listen_sslctx_delete_ticket_keys(void) { @@ -1957,6 +1961,7 @@ listen_sslctx_delete_ticket_keys(void) free(ticket_keys); ticket_keys = NULL; } +#endif /* HAVE_SSL */ # ifndef USE_WINSOCK char* From ed71f72baae8d677c271c96a528a19ce433ca4dc Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 4 Apr 2025 10:57:53 +0200 Subject: [PATCH 091/218] - Fix redis_replicat test for unused option defaults and log printout. --- doc/Changelog | 1 + testdata/redis_replica.tdir/redis.conf | 18 +++++++++--------- testdata/redis_replica.tdir/redis_replica.post | 7 +++++++ testdata/redis_replica.tdir/redis_replica.test | 2 -- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 301d53f8a..c90a1dd1d 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,7 @@ - Fix mesh_copy_client_info to omit null contents from copy. - Fix comment name in the rpz nsdname test. - Fix nettle compile for warnings and ticket keys. + - Fix redis_replicat test for unused option defaults and log printout. 3 April 2025: Wouter - Fix #1263: Exempt loopback addresses from wait-limit. diff --git a/testdata/redis_replica.tdir/redis.conf b/testdata/redis_replica.tdir/redis.conf index 20a3133f8..3b80736e2 100644 --- a/testdata/redis_replica.tdir/redis.conf +++ b/testdata/redis_replica.tdir/redis.conf @@ -120,7 +120,7 @@ proc-title-template "{title} {listen-addr} {server-mode}" # Set the local environment which is used for string comparison operations, and # also affect the performance of Lua scripts. Empty String indicates the locale # is derived from the environment variables. -locale-collate "" +#locale-collate "" # By default Redis will stop accepting writes if RDB snapshots are enabled # (at least one save point) and the latest background save failed. @@ -245,7 +245,7 @@ repl-diskless-sync-delay 5 # the replication start before the maximum delay is reached if the maximum # number of replicas expected have connected. Default of 0 means that the # maximum is not defined and Redis will wait the full delay. -repl-diskless-sync-max-replicas 0 +#repl-diskless-sync-max-replicas 0 # ----------------------------------------------------------------------------- # WARNING: Since in this setup the replica does not immediately store an RDB on @@ -434,8 +434,8 @@ notify-keyspace-events "" # Hashes are encoded using a memory efficient data structure when they have a # small number of entries, and the biggest entry does not exceed a given # threshold. These thresholds can be configured using the following directives. -hash-max-listpack-entries 512 -hash-max-listpack-value 64 +#hash-max-listpack-entries 512 +#hash-max-listpack-value 64 # Lists are also encoded in a special way to save a lot of space. # The number of entries allowed per internal list node can be specified @@ -450,7 +450,7 @@ hash-max-listpack-value 64 # per list node. # The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), # but if your use case is unique, adjust the settings as necessary. -list-max-listpack-size -2 +#list-max-listpack-size -2 # Lists may also be compressed. # Compress depth is the number of quicklist ziplist nodes from *each* side of @@ -479,14 +479,14 @@ set-max-intset-entries 512 # data structure when they have a small number of entries, and the biggest entry # does not exceed a given threshold. These thresholds can be configured using # the following directives. -set-max-listpack-entries 128 -set-max-listpack-value 64 +#set-max-listpack-entries 128 +#set-max-listpack-value 64 # Similarly to hashes and lists, sorted sets are also specially encoded in # order to save a lot of space. This encoding is only used when the length and # elements of a sorted set are below the following limits: -zset-max-listpack-entries 128 -zset-max-listpack-value 64 +#zset-max-listpack-entries 128 +#zset-max-listpack-value 64 # HyperLogLog sparse representation bytes limit. The limit includes the # 16 bytes header. When a HyperLogLog using the sparse representation crosses diff --git a/testdata/redis_replica.tdir/redis_replica.post b/testdata/redis_replica.tdir/redis_replica.post index afb41f19f..35f116518 100644 --- a/testdata/redis_replica.tdir/redis_replica.post +++ b/testdata/redis_replica.tdir/redis_replica.post @@ -9,3 +9,10 @@ kill_pid $REDIS_PID kill_pid $REDIS_REPLICA_PID kill_pid $UNBOUND_PID +echo "> cat logfiles" +echo "redis server.log" +cat server.log +echo "redis replica.log" +cat replica.log +echo "unbound.log" +cat unbound.log diff --git a/testdata/redis_replica.tdir/redis_replica.test b/testdata/redis_replica.tdir/redis_replica.test index c3e141dd9..a9f15b809 100644 --- a/testdata/redis_replica.tdir/redis_replica.test +++ b/testdata/redis_replica.tdir/redis_replica.test @@ -74,7 +74,5 @@ redis_cli_check_keys $REDIS_SOCKET 1 # check Redis replica server has no keys redis_cli_check_keys $REDIS_REPLICA_SOCKET 0 -echo "> cat logfiles" -cat unbound.log echo "> OK" exit 0 From 349721a39de6d58b9227a446475672af2d9d83aa Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 4 Apr 2025 10:58:05 +0200 Subject: [PATCH 092/218] Changelog entry for #1019: - Merge #1019: Redis read-only replica support. Introduces new 'redis-replica-*' options for the Redis cache backend. --- doc/Changelog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index c90a1dd1d..fadef8bed 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -4,6 +4,10 @@ - Fix nettle compile for warnings and ticket keys. - Fix redis_replicat test for unused option defaults and log printout. +4 April 2025: Yorgos + - Merge #1019: Redis read-only replica support. + Introduces new 'redis-replica-*' options for the Redis cache backend. + 3 April 2025: Wouter - Fix #1263: Exempt loopback addresses from wait-limit. - Fix wait-limit-netblock and wait-limit-cookie-netblock config parse From a7e618b9c29abe569b9efa3785f36d417345d6dc Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 4 Apr 2025 11:00:16 +0200 Subject: [PATCH 093/218] Fix typo in Changelog entry. --- doc/Changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index fadef8bed..e60226696 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,7 +2,7 @@ - Fix mesh_copy_client_info to omit null contents from copy. - Fix comment name in the rpz nsdname test. - Fix nettle compile for warnings and ticket keys. - - Fix redis_replicat test for unused option defaults and log printout. + - Fix redis_replica test for unused option defaults and log printout. 4 April 2025: Yorgos - Merge #1019: Redis read-only replica support. From 4074a5b2ba18bc1af272efbade381e85b1f4044a Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 4 Apr 2025 11:37:42 +0200 Subject: [PATCH 094/218] - Fix test to speed up common.sh script kill_pid. --- doc/Changelog | 1 + testdata/common.sh | 3 +++ 2 files changed, 4 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index e60226696..40c58eb48 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -3,6 +3,7 @@ - Fix comment name in the rpz nsdname test. - Fix nettle compile for warnings and ticket keys. - Fix redis_replica test for unused option defaults and log printout. + - Fix test to speed up common.sh script kill_pid. 4 April 2025: Yorgos - Merge #1019: Redis read-only replica support. diff --git a/testdata/common.sh b/testdata/common.sh index bf2d301eb..3f9f9a894 100644 --- a/testdata/common.sh +++ b/testdata/common.sh @@ -309,6 +309,7 @@ kill_pid () { local WAIT_THRES=30 local try kill $1 + sleep .001 for (( try=0 ; try <= $MAX_DOWN_TRY ; try++ )) ; do if kill -0 $1 >/dev/null 2>&1; then : @@ -322,6 +323,8 @@ kill_pid () { fi if test $try -ge $WAIT_THRES; then sleep 1 + else + sleep .01 fi # re-send the signal kill $1 >/dev/null 2>&1 From eb390dd0381b7f502c52664c7984c13cafbd62d4 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 4 Apr 2025 11:55:42 +0200 Subject: [PATCH 095/218] - Fix to update common.sh for speed of kill_pid. --- doc/Changelog | 1 + testdata/common.sh | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index 40c58eb48..d6fee9b08 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -4,6 +4,7 @@ - Fix nettle compile for warnings and ticket keys. - Fix redis_replica test for unused option defaults and log printout. - Fix test to speed up common.sh script kill_pid. + - Fix to update common.sh for speed of kill_pid. 4 April 2025: Yorgos - Merge #1019: Redis read-only replica support. diff --git a/testdata/common.sh b/testdata/common.sh index 3f9f9a894..48b8204d2 100644 --- a/testdata/common.sh +++ b/testdata/common.sh @@ -1,7 +1,8 @@ # common.sh - an include file for commonly used functions for test code. # BSD licensed (see LICENSE file). # -# Version 6 +# Version 7 +# 2025-04-04: speed up kill_pid. # 2023-12-06: list wait_for_soa_serial in overview # 2023-12-06: get_ldns_notify, skip_test and teststep, and previous changes # also included are wait_logfile, cpu_count, process_cpu_list, and From a616437338fd3a61f38ed171aad5171f05abd3ec Mon Sep 17 00:00:00 2001 From: Willem Toorop Date: Mon, 7 Apr 2025 10:25:10 +0200 Subject: [PATCH 096/218] DNS Error Reporting (RFC 9567) (#902) * v1 EDER poc * remove superfluous edns_list_get_option function * create an EDER configurable * Hackathon 114 * Fixes for version -04 * Generated configparser and configlexer are not versioned in master anymore * Remove NOERROR DNS Error Reporting; not part of final RFC. * Use assigned IANA EDNS0 Option Code for Report-Channel. * Fix buffer protection and agent domain validity * Use DNS Error Reporting instead of the eder nickname * Update documentation. * Fix typo. * Bail out early if ede is not present. * Forget previous EDNS options from upstream; this is what was implicitly happening but not deterministacally. * Don't report LDNS_EDE_OTHER and bail early if there is no reporting agent. * Only do DNS error reporting when a client asked for something that went wrong. * Add an error reporting agent in the parent that should be ignored. * review feedback. * fixup for fast reload * Add 'num.dns_error_reports' to stats and test for it. --------- Co-authored-by: TCY16 Co-authored-by: Yorgos Thessalonikefs --- daemon/remote.c | 3 + daemon/stats.c | 7 +- doc/example.conf.in | 5 + doc/unbound-control.8.in | 6 + doc/unbound.conf.5.in | 25 ++- iterator/iterator.c | 1 + libunbound/unbound.h | 2 + services/mesh.c | 117 ++++++++++ services/mesh.h | 2 + sldns/rrdef.h | 1 + smallapp/unbound-control.c | 11 +- testdata/dns_error_reporting.rpl | 200 ++++++++++++++++++ testdata/stat_values.tdir/stat_values.conf | 9 + testdata/stat_values.tdir/stat_values.test | 33 ++- testdata/stat_values.tdir/stat_values.testns | 45 +++- .../stat_values_discard_wait_limit.conf | 2 +- util/config_file.c | 5 +- util/config_file.h | 6 +- util/configlexer.lex | 1 + util/configparser.y | 11 + 20 files changed, 469 insertions(+), 23 deletions(-) create mode 100644 testdata/dns_error_reporting.rpl diff --git a/daemon/remote.c b/daemon/remote.c index df1f0d4e6..50bdefd68 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -824,6 +824,8 @@ print_stats(RES* ssl, const char* nm, struct ub_stats_info* s) if(!ssl_printf(ssl, "%s.num.dnscrypt.malformed"SQ"%lu\n", nm, (unsigned long)s->svr.num_query_dnscrypt_crypted_malformed)) return 0; #endif + if(!ssl_printf(ssl, "%s.num.dns_error_reports"SQ"%lu\n", nm, + (unsigned long)s->svr.num_dns_error_reports)) return 0; if(!ssl_printf(ssl, "%s.requestlist.avg"SQ"%g\n", nm, (s->svr.num_queries_missed_cache+s->svr.num_queries_prefetch)? (double)s->svr.sum_query_list_size/ @@ -5639,6 +5641,7 @@ fr_atomic_copy_cfg(struct config_file* oldcfg, struct config_file* cfg, COPY_VAR_int(serve_expired_reply_ttl); COPY_VAR_int(serve_expired_client_timeout); COPY_VAR_int(ede_serve_expired); + COPY_VAR_int(dns_error_reporting); COPY_VAR_int(serve_original_ttl); COPY_VAR_ptr(val_nsec3_key_iterations); COPY_VAR_int(zonemd_permissive_mode); diff --git a/daemon/stats.c b/daemon/stats.c index 3f2d848b3..7efb83a0b 100644 --- a/daemon/stats.c +++ b/daemon/stats.c @@ -285,6 +285,8 @@ server_stats_compile(struct worker* worker, struct ub_stats_info* s, int reset) (long long)worker->env.mesh->num_queries_discard_timeout; s->svr.num_queries_wait_limit += (long long)worker->env.mesh->num_queries_wait_limit; + s->svr.num_dns_error_reports += + (long long)worker->env.mesh->num_dns_error_reports; /* values from outside network */ s->svr.unwanted_replies = (long long)worker->back->unwanted_replies; s->svr.qtcp_outgoing = (long long)worker->back->num_tcp_outgoing; @@ -446,6 +448,7 @@ void server_stats_add(struct ub_stats_info* total, struct ub_stats_info* a) total->svr.num_queries_discard_timeout += a->svr.num_queries_discard_timeout; total->svr.num_queries_wait_limit += a->svr.num_queries_wait_limit; + total->svr.num_dns_error_reports += a->svr.num_dns_error_reports; total->svr.num_queries_missed_cache += a->svr.num_queries_missed_cache; total->svr.num_queries_prefetch += a->svr.num_queries_prefetch; total->svr.num_queries_timed_out += a->svr.num_queries_timed_out; @@ -458,9 +461,9 @@ void server_stats_add(struct ub_stats_info* total, struct ub_stats_info* a) #ifdef USE_DNSCRYPT total->svr.num_query_dnscrypt_crypted += a->svr.num_query_dnscrypt_crypted; total->svr.num_query_dnscrypt_cert += a->svr.num_query_dnscrypt_cert; - total->svr.num_query_dnscrypt_cleartext += \ + total->svr.num_query_dnscrypt_cleartext += a->svr.num_query_dnscrypt_cleartext; - total->svr.num_query_dnscrypt_crypted_malformed += \ + total->svr.num_query_dnscrypt_crypted_malformed += a->svr.num_query_dnscrypt_crypted_malformed; #endif /* USE_DNSCRYPT */ /* the max size reached is upped to higher of both */ diff --git a/doc/example.conf.in b/doc/example.conf.in index 6eabbe5fd..fdef9ef37 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -1086,6 +1086,11 @@ server: # Note that the ede option above needs to be enabled for this to work. # ede-serve-expired: no + # Enable DNS Error Reporting (RFC9567). + # qname-minimisation is advised to be turned on as well to increase + # privacy on the outgoing reports. + # dns-error-reporting: no + # Specific options for ipsecmod. Unbound needs to be configured with # --enable-ipsecmod for these to take effect. # diff --git a/doc/unbound-control.8.in b/doc/unbound-control.8.in index 22a121414..6c0cdc217 100644 --- a/doc/unbound-control.8.in +++ b/doc/unbound-control.8.in @@ -534,6 +534,9 @@ request for certificates. .I threadX.num.dnscrypt.malformed number of request that were neither cleartext, not valid dnscrypt messages. .TP +.I threadX.num.dns_error_reports +number of DNS Error Reports generated by thread +.TP .I threadX.num.prefetch number of cache prefetches performed. This number is included in cachehits, as the original query had the unprefetched answer from cache, @@ -628,6 +631,9 @@ summed over threads. .I total.num.dnscrypt.malformed summed over threads. .TP +.I total.num.dns_error_reports +summed over threads. +.TP .I total.num.prefetch summed over threads. .TP diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index e65125a63..21dbd73e6 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -2089,17 +2089,30 @@ be used. Default is 65001. .TP 5 .B ede: \fI If enabled, Unbound will respond with Extended DNS Error codes (RFC8914). -These EDEs attach informative error messages to a response for various -errors. Default is "no". +These EDEs provide additional information with a response mainly for, but not +limited to, DNS and DNSSEC errors. When the \fBval-log-level\fR option is also set to \fB2\fR, responses with -Extended DNS Errors concerning DNSSEC failures that are not served from cache, -will also contain a descriptive text message about the reason for the failure. +Extended DNS Errors concerning DNSSEC failures will also contain a descriptive +text message about the reason for the failure. +Default is "no". .TP 5 .B ede\-serve\-expired: \fI If enabled, Unbound will attach an Extended DNS Error (RFC8914) Code 3 - Stale -Answer as EDNS0 option to the expired response. Note that this will not attach -the EDE code without setting the global \fBede\fR option to "yes" as well. +Answer as EDNS0 option to the expired response. +The \fBede\fR option needs to be enabled as well for this to work. +Default is "no". +.TP 5 +.B dns\-error\-reporting: \fI +If enabled, Unbound will send DNS Error Reports (RFC9567). +The name servers need to express support by attaching the Report-Channel EDNS0 +option on their replies specifying the reporting agent for the zone. +Any errors encountered during resolution that would result in Unbound +generating an Extended DNS Error (RFC8914) will be reported to the zone's +reporting agent. +The \fBede\fR option does not need to be enabled for this to work. +It is advised that the \fBqname\-minimisation\fR option is also enabled to +increase privacy on the outgoing reports. Default is "no". .SS "Remote Control Options" In the diff --git a/iterator/iterator.c b/iterator/iterator.c index 8c0703e9e..e64dfa61b 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -4332,6 +4332,7 @@ process_response(struct module_qstate* qstate, struct iter_qstate* iq, } /* Copy the edns options we may got from the back end */ + qstate->edns_opts_back_in = NULL; if(edns.opt_list_in) { qstate->edns_opts_back_in = edns_opt_copy_region(edns.opt_list_in, qstate->region); diff --git a/libunbound/unbound.h b/libunbound/unbound.h index 8a1625b9f..bdcf4edec 100644 --- a/libunbound/unbound.h +++ b/libunbound/unbound.h @@ -853,6 +853,8 @@ struct ub_server_stats { long long num_queries_discard_timeout; /** number of queries removed due to wait-limit */ long long num_queries_wait_limit; + /** number of dns error reports generated */ + long long num_dns_error_reports; }; /** diff --git a/services/mesh.c b/services/mesh.c index b62aa5c17..1d19e7c7d 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -232,6 +232,7 @@ mesh_create(struct module_stack* stack, struct module_env* env) mesh->ans_cachedb = 0; mesh->num_queries_discard_timeout = 0; mesh->num_queries_wait_limit = 0; + mesh->num_dns_error_reports = 0; mesh->max_reply_states = env->cfg->num_queries_per_thread; mesh->max_forever_states = (mesh->max_reply_states+1)/2; #ifndef S_SPLINT_S @@ -1582,6 +1583,117 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, } } +/** + * Generate the DNS Error Report (RFC9567). + * If there is an EDE attached for this reply and there was a Report-Channel + * EDNS0 option from the upstream, fire up a report query. + * @param qstate: module qstate. + * @param rep: prepared reply to be sent. + */ +static void dns_error_reporting(struct module_qstate* qstate, + struct reply_info* rep) +{ + struct query_info qinfo; + struct mesh_state* sub; + struct module_qstate* newq; + uint8_t buf[LDNS_MAX_DOMAINLEN]; + size_t count = 0; + int written; + size_t expected_length; + struct edns_option* opt; + sldns_ede_code reason_bogus = LDNS_EDE_NONE; + sldns_rr_type qtype = qstate->qinfo.qtype; + uint8_t* qname = qstate->qinfo.qname; + size_t qname_len = qstate->qinfo.qname_len-1; /* skip the trailing \0 */ + uint8_t* agent_domain; + size_t agent_domain_len; + + /* We need a valid reporting agent; + * this is based on qstate->edns_opts_back_in that will probably have + * the latest reporting agent we found while iterating */ + opt = edns_opt_list_find(qstate->edns_opts_back_in, + LDNS_EDNS_REPORT_CHANNEL); + if(!opt) return; + agent_domain_len = opt->opt_len; + agent_domain = opt->opt_data; + if(dname_valid(agent_domain, agent_domain_len) < 3) { + /* The agent domain needs to be a valid dname that is not the + * root; from RFC9567. */ + return; + } + + /* Get the EDE generated from the mesh state, these are mostly + * validator errors. If other errors are produced in the future (e.g., + * RPZ) we would not want them to result in error reports. */ + reason_bogus = errinf_to_reason_bogus(qstate); + if(rep && ((reason_bogus == LDNS_EDE_DNSSEC_BOGUS && + rep->reason_bogus != LDNS_EDE_NONE) || + reason_bogus == LDNS_EDE_NONE)) { + reason_bogus = rep->reason_bogus; + } + if(reason_bogus == LDNS_EDE_NONE || + /* other, does not make sense without the text that comes + * with it */ + reason_bogus == LDNS_EDE_OTHER) return; + + /* Synthesize the error report query in the format: + * "_er.$qtype.$qname.$ede._er.$reporting-agent-domain" */ + /* First check if the static length parts fit in the buffer. + * That is everything except for qtype and ede that need to be + * converted to decimal and checked further on. */ + expected_length = 4/*_er*/+qname_len+4/*_er*/+agent_domain_len; + if(expected_length > LDNS_MAX_DOMAINLEN) goto skip; + + memmove(buf+count, "\3_er", 4); + count += 4; + + written = snprintf((char*)buf+count, LDNS_MAX_DOMAINLEN-count, + "X%d", qtype); + expected_length += written; + /* Skip on error, truncation or long expected length */ + if(written < 0 || (size_t)written >= LDNS_MAX_DOMAINLEN-count || + expected_length > LDNS_MAX_DOMAINLEN ) goto skip; + /* Put in the label length */ + *(buf+count) = (char)(written - 1); + count += written; + + memmove(buf+count, qname, qname_len); + count += qname_len; + + written = snprintf((char*)buf+count, LDNS_MAX_DOMAINLEN-count, + "X%d", reason_bogus); + expected_length += written; + /* Skip on error, truncation or long expected length */ + if(written < 0 || (size_t)written >= LDNS_MAX_DOMAINLEN-count || + expected_length > LDNS_MAX_DOMAINLEN ) goto skip; + *(buf+count) = (char)(written - 1); + count += written; + + memmove(buf+count, "\3_er", 4); + count += 4; + + /* Copy the agent domain */ + memmove(buf+count, agent_domain, agent_domain_len); + count += agent_domain_len; + + qinfo.qname = buf; + qinfo.qname_len = count; + qinfo.qtype = LDNS_RR_TYPE_TXT; + qinfo.qclass = qstate->qinfo.qclass; + qinfo.local_alias = NULL; + + log_query_info(VERB_ALGO, "DNS Error Reporting: generating report " + "query for", &qinfo); + if(mesh_add_sub(qstate, &qinfo, BIT_RD, 0, 0, &newq, &sub)) { + qstate->env->mesh->num_dns_error_reports++; + } + return; +skip: + verbose(VERB_ALGO, "DNS Error Reporting: report query qname too long; " + "skip"); + return; +} + void mesh_query_done(struct mesh_state* mstate) { struct mesh_reply* r; @@ -1610,6 +1722,10 @@ void mesh_query_done(struct mesh_state* mstate) if(err) { log_err("%s", err); } } } + + if(mstate->reply_list && mstate->s.env->cfg->dns_error_reporting) + dns_error_reporting(&mstate->s, rep); + for(r = mstate->reply_list; r; r = r->next) { struct timeval old; timeval_subtract(&old, mstate->s.env->now_tv, &r->start_time); @@ -2156,6 +2272,7 @@ mesh_stats_clear(struct mesh_area* mesh) mesh->ans_nodata = 0; mesh->num_queries_discard_timeout = 0; mesh->num_queries_wait_limit = 0; + mesh->num_dns_error_reports = 0; } size_t diff --git a/services/mesh.h b/services/mesh.h index 0b01d4ef8..fd17c05da 100644 --- a/services/mesh.h +++ b/services/mesh.h @@ -141,6 +141,8 @@ struct mesh_area { size_t num_queries_discard_timeout; /** stats, number of queries removed due to wait-limit */ size_t num_queries_wait_limit; + /** stats, number of dns error reports generated */ + size_t num_dns_error_reports; /** backup of query if other operations recurse and need the * network buffers */ diff --git a/sldns/rrdef.h b/sldns/rrdef.h index 24abec622..540468889 100644 --- a/sldns/rrdef.h +++ b/sldns/rrdef.h @@ -443,6 +443,7 @@ enum sldns_enum_edns_option LDNS_EDNS_PADDING = 12, /* RFC7830 */ LDNS_EDNS_EDE = 15, /* RFC8914 */ LDNS_EDNS_CLIENT_TAG = 16, /* draft-bellis-dnsop-edns-tags-01 */ + LDNS_EDNS_REPORT_CHANNEL = 18, /* RFC9567 */ LDNS_EDNS_UNBOUND_CACHEDB_TESTFRAME_TEST = 65534 }; typedef enum sldns_enum_edns_option sldns_edns_option; diff --git a/smallapp/unbound-control.c b/smallapp/unbound-control.c index dcbe66030..0136b5e4e 100644 --- a/smallapp/unbound-control.c +++ b/smallapp/unbound-control.c @@ -244,12 +244,13 @@ static void pr_stats(const char* nm, struct ub_stats_info* s) PR_UL_NM("num.expired", s->svr.ans_expired); PR_UL_NM("num.recursivereplies", s->mesh_replies_sent); #ifdef USE_DNSCRYPT - PR_UL_NM("num.dnscrypt.crypted", s->svr.num_query_dnscrypt_crypted); - PR_UL_NM("num.dnscrypt.cert", s->svr.num_query_dnscrypt_cert); - PR_UL_NM("num.dnscrypt.cleartext", s->svr.num_query_dnscrypt_cleartext); - PR_UL_NM("num.dnscrypt.malformed", - s->svr.num_query_dnscrypt_crypted_malformed); + PR_UL_NM("num.dnscrypt.crypted", s->svr.num_query_dnscrypt_crypted); + PR_UL_NM("num.dnscrypt.cert", s->svr.num_query_dnscrypt_cert); + PR_UL_NM("num.dnscrypt.cleartext", s->svr.num_query_dnscrypt_cleartext); + PR_UL_NM("num.dnscrypt.malformed", + s->svr.num_query_dnscrypt_crypted_malformed); #endif /* USE_DNSCRYPT */ + PR_UL_NM("num.dns_error_reports", s->svr.num_dns_error_reports); printf("%s.requestlist.avg"SQ"%g\n", nm, (s->svr.num_queries_missed_cache+s->svr.num_queries_prefetch)? (double)s->svr.sum_query_list_size/ diff --git a/testdata/dns_error_reporting.rpl b/testdata/dns_error_reporting.rpl new file mode 100644 index 000000000..f1fac12a2 --- /dev/null +++ b/testdata/dns_error_reporting.rpl @@ -0,0 +1,200 @@ +; Test DNS Error Reporting. + +server: + module-config: "validator iterator" + trust-anchor-signaling: no + target-fetch-policy: "0 0 0 0 0" + verbosity: 4 + qname-minimisation: no + minimal-responses: no + rrset-roundrobin: no + trust-anchor: "a.domain DS 50602 8 2 FA8EE175C47325F4BD46D8A4083C3EBEB11C977D689069F2B41F1A29B22446B1" + ede: no # It is not needed for dns-error-reporting; only for clients to receive EDEs + dns-error-reporting: yes + do-ip6: no + +stub-zone: + name: domain + stub-addr: 0.0.0.0 +stub-zone: + name: an.agent + stub-addr: 0.0.0.2 +CONFIG_END + +SCENARIO_BEGIN Test DNS Error Reporting + +; domain +RANGE_BEGIN 0 100 + ADDRESS 0.0.0.0 + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + a.domain. IN A + SECTION AUTHORITY + a.domain. IN NS ns.a.domain. + SECTION ADDITIONAL + ns.a.domain. IN A 0.0.0.1 + HEX_EDNSDATA_BEGIN + 00 12 ; opt-code (Report-Channel) + 00 0A ; opt-len + 02 61 6E 05 61 67 65 6E 74 00 ; an.agent. + HEX_EDNSDATA_END + ENTRY_END +RANGE_END + +; a.domain +RANGE_BEGIN 0 9 + ADDRESS 0.0.0.1 + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + a.domain. IN DNSKEY + ENTRY_END + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + a.domain. IN A + SECTION ANSWER + a.domain. 5 IN A 0.0.0.0 + ; No RRSIG to trigger validation error (and EDE) + SECTION ADDITIONAL + ; No Report-Channel here + ENTRY_END +RANGE_END + +; a.domain +RANGE_BEGIN 10 100 + ADDRESS 0.0.0.1 + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + a.domain. IN DNSKEY + ENTRY_END + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + a.domain. IN A + SECTION ANSWER + a.domain. 5 IN A 0.0.0.0 + ; No RRSIG to trigger validator error and EDE + SECTION ADDITIONAL + HEX_EDNSDATA_BEGIN + 00 12 ; opt-code (Report-Channel) + 00 0A ; opt-len + 02 61 6E 05 61 67 65 6E 74 00 ; an.agent. + HEX_EDNSDATA_END + ENTRY_END +RANGE_END + +; an.agent +RANGE_BEGIN 10 20 + ADDRESS 0.0.0.2 + ENTRY_BEGIN + MATCH opcode qtype qname + ADJUST copy_id + REPLY QR NOERROR + SECTION QUESTION + _er.1.a.domain.9._er.an.agent. IN TXT + SECTION ANSWER + _er.1.a.domain.9._er.an.agent. IN TXT "OK" + ENTRY_END +RANGE_END + +; Query +STEP 0 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.domain. IN A +ENTRY_END + +; Check that validation failed (no DNS error reporting at this state; +; 'domain' did give an error reporting agent, but the latest upstream +; 'a.domain' did not) +STEP 1 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA SERVFAIL +SECTION QUESTION +a.domain. IN A +ENTRY_END + +; Wait for the a.domain query to expire (TTL 5) +STEP 3 TIME_PASSES ELAPSE 6 + +; Query again +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.domain. IN A +ENTRY_END + +; Check that validation failed +; (a DNS Error Report query should have been generated) +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA SERVFAIL +SECTION QUESTION +a.domain. IN A +ENTRY_END + +; Check explicitly that the DNS Error Report query is cached. +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +_er.1.a.domain.9._er.an.agent. IN TXT +ENTRY_END + +; At this range there are no configured agents to answer this. +; If the DNS Error Report query is not answered from the cache the test will +; fail with pending messages. +STEP 21 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY RD QR RA NOERROR +SECTION QUESTION +_er.1.a.domain.9._er.an.agent. IN TXT +SECTION ANSWER +_er.1.a.domain.9._er.an.agent. IN TXT "OK" +ENTRY_END + +; Wait for the a.domain query to expire (5 TTL). +; The DNS Error Report query should still be cached (SOA negative). +STEP 30 TIME_PASSES ELAPSE 6 + +; Force a DNS Error Report query generation again. +STEP 31 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.domain. IN A +ENTRY_END + +; Check that validation failed +STEP 32 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA SERVFAIL +SECTION QUESTION +a.domain. IN A +ENTRY_END + +; The same DNS Error Report query will be generated as above. +; No agent is configured at this range to answer the DNS Error Report query. +; If the DNS Error Report query is not used from the cache the test will fail +; with pending messages. + +SCENARIO_END diff --git a/testdata/stat_values.tdir/stat_values.conf b/testdata/stat_values.tdir/stat_values.conf index d1adff58c..312a7e174 100644 --- a/testdata/stat_values.tdir/stat_values.conf +++ b/testdata/stat_values.tdir/stat_values.conf @@ -15,6 +15,9 @@ server: root-key-sentinel: no trust-anchor-signaling: no serve-expired-client-timeout: 0 + dns-error-reporting: yes + + trust-anchor: "bogusdnssec. DS 1444 8 2 5224fb17d630a2e3efdc863a05a4032c5db415b5de3f32472ee9abed42e10146" local-zone: local.zone static local-data: "www.local.zone A 192.0.2.1" @@ -30,6 +33,12 @@ remote-control: stub-zone: name: "example.com." stub-addr: "127.0.0.1@@TOPORT@" +stub-zone: + name: "bogusdnssec." + stub-addr: "127.0.0.1@@TOPORT@" +stub-zone: + name: "an.agent." + stub-addr: "127.0.0.1@@TOPORT@" stub-zone: name: "expired." stub-addr: "127.0.0.1@@EXPIREDPORT@" diff --git a/testdata/stat_values.tdir/stat_values.test b/testdata/stat_values.tdir/stat_values.test index 456d27cb8..d538e4d60 100644 --- a/testdata/stat_values.tdir/stat_values.test +++ b/testdata/stat_values.tdir/stat_values.test @@ -426,6 +426,35 @@ rrset.cache.count=3 infra.cache.count=2" +teststep "Check dns-error-reporting." +echo "> dig www.bogusdnssec." +dig @127.0.0.1 -p $UNBOUND_PORT www.bogusdnssec. | tee outfile +echo "> check answer" +if grep "SERVFAIL" outfile; then + echo "OK" +else + end 1 +fi +check_stats "\ +infra.cache.count=4 +key.cache.count=1 +msg.cache.count=7 +num.answer.bogus=1 +num.answer.rcode.SERVFAIL=1 +num.query.class.IN=1 +num.query.edns.present=1 +num.query.flags.AD=1 +num.query.flags.RD=1 +num.query.opcode.QUERY=1 +num.query.type.A=1 +num.query.udpout=9 +rrset.cache.count=4 +total.num.cachemiss=1 +total.num.dns_error_reports=1 +total.num.queries=1 +total.num.recursivereplies=1" + + ### # # Bring the discard-timeout, wait-limit configured Unbound up @@ -436,8 +465,8 @@ bring_up_alternate_configuration ub_discard_wait_limit.conf teststep "Check discard-timeout and wait-limit" -echo "> dig www.slow" -dig @127.0.0.1 -p $UNBOUND_PORT +retry=2 +timeout=1 www.slow. | tee outfile +echo "> dig www.unresponsive" +dig @127.0.0.1 -p $UNBOUND_PORT +retry=2 +timeout=1 www.unresponsive. | tee outfile echo "> check answer" if grep "no servers could be reached" outfile; then echo "OK" diff --git a/testdata/stat_values.tdir/stat_values.testns b/testdata/stat_values.tdir/stat_values.testns index 906c49f2b..a5c0ae92b 100644 --- a/testdata/stat_values.tdir/stat_values.testns +++ b/testdata/stat_values.tdir/stat_values.testns @@ -32,14 +32,51 @@ SECTION ANSWER 0ttl 0 IN A 0.0.0.1 ENTRY_END -$ORIGIN slow. + + +$ORIGIN bogusdnssec. ENTRY_BEGIN MATCH opcode qtype qname REPLY QR AA NOERROR -ADJUST copy_id sleep=2 +ADJUST copy_id SECTION QUESTION -www. IN A +@ IN DNSKEY SECTION ANSWER -www. 0 IN A 10.20.30.40 ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NOERROR +ADJUST copy_id +SECTION QUESTION +www IN A +SECTION ANSWER +www 0 IN A 10.20.30.40 +; bogus signature to not trigger LAME DNSSEC and continue with validation +www 0 IN RRSIG A 8 2 240 20250429005000 20250401005000 42393 bogusdnssec. ob6ddTJkdeOUn92cxx1NPGneV7rhOp2zKBv8FXQjJ/Wso8LJJnzRHW9p 3sTatlzi+UdRi7BOrcxwjUG38lgO+TS5vRFGAiTRmOezm6xJVNTg8lIb RJGCD5bRtRRstwt31Qt6Gda+6sAyvDebpUB/opkQpevv6xohdrhr0g8+ Q4w= +SECTION ADDITIONAL +; dns error reporting agent +HEX_EDNSDATA_BEGIN + 00 12 ; opt-code (Report-Channel) + 00 0A ; opt-len + 02 61 6E 05 61 67 65 6E 74 00 ; an.agent. +HEX_EDNSDATA_END +ENTRY_END + + + +$ORIGIN an.agent. +;just give an answer back to anything +ENTRY_BEGIN +MATCH opcode subdomain +REPLY QR AA NXDOMAIN +ADJUST copy_id copy_query +SECTION QUESTION +an.agent. IN ANY +ENTRY_END + + + +$ORIGIN unresponsive. +;; no entry for 'unresponsive.', we rely on timeouts. diff --git a/testdata/stat_values.tdir/stat_values_discard_wait_limit.conf b/testdata/stat_values.tdir/stat_values_discard_wait_limit.conf index d4a3459b8..b6f63cf17 100644 --- a/testdata/stat_values.tdir/stat_values_discard_wait_limit.conf +++ b/testdata/stat_values.tdir/stat_values_discard_wait_limit.conf @@ -32,5 +32,5 @@ remote-control: control-key-file: "unbound_control.key" control-cert-file: "unbound_control.pem" stub-zone: - name: "slow." + name: "unresponsive." stub-addr: "127.0.0.1@@TOPORT@" diff --git a/util/config_file.c b/util/config_file.c index a24067060..81bffa8d8 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -284,7 +284,6 @@ config_create(void) cfg->serve_expired_ttl_reset = 0; cfg->serve_expired_reply_ttl = 30; cfg->serve_expired_client_timeout = 1800; - cfg->ede_serve_expired = 0; cfg->serve_original_ttl = 0; cfg->zonemd_permissive_mode = 0; cfg->add_holddown = 30*24*3600; @@ -418,6 +417,8 @@ config_create(void) cfg->ipset_name_v6 = NULL; #endif cfg->ede = 0; + cfg->ede_serve_expired = 0; + cfg->dns_error_reporting = 0; cfg->iter_scrub_ns = 20; cfg->iter_scrub_cname = 11; cfg->max_global_quota = 200; @@ -756,6 +757,7 @@ int config_set_option(struct config_file* cfg, const char* opt, else S_NUMBER_OR_ZERO("serve-expired-client-timeout:", serve_expired_client_timeout) else S_YNO("ede:", ede) else S_YNO("ede-serve-expired:", ede_serve_expired) + else S_YNO("dns-error-reporting:", dns_error_reporting) else S_NUMBER_OR_ZERO("iter-scrub-ns:", iter_scrub_ns) else S_NUMBER_OR_ZERO("iter-scrub-cname:", iter_scrub_cname) else S_NUMBER_OR_ZERO("max-global-quota:", max_global_quota) @@ -1231,6 +1233,7 @@ config_get_option(struct config_file* cfg, const char* opt, else O_DEC(opt, "serve-expired-client-timeout", serve_expired_client_timeout) else O_YNO(opt, "ede", ede) else O_YNO(opt, "ede-serve-expired", ede_serve_expired) + else O_YNO(opt, "dns-error-reporting", dns_error_reporting) else O_DEC(opt, "iter-scrub-ns", iter_scrub_ns) else O_DEC(opt, "iter-scrub-cname", iter_scrub_cname) else O_DEC(opt, "max-global-quota", max_global_quota) diff --git a/util/config_file.h b/util/config_file.h index a5d73f4c6..89bbc1c7d 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -438,8 +438,6 @@ struct config_file { /** serve expired entries only after trying to update the entries and this * timeout (in milliseconds) is reached */ int serve_expired_client_timeout; - /** serve EDE code 3 - Stale Answer (RFC8914) for expired entries */ - int ede_serve_expired; /** serve original TTLs rather than decrementing ones */ int serve_original_ttl; /** nsec3 maximum iterations per key size, string */ @@ -784,6 +782,10 @@ struct config_file { #endif /** respond with Extended DNS Errors (RFC8914) */ int ede; + /** serve EDE code 3 - Stale Answer (RFC8914) for expired entries */ + int ede_serve_expired; + /** send DNS Error Reports to upstream reporting agent (RFC9567) */ + int dns_error_reporting; /** limit on NS RRs in RRset for the iterator scrubber. */ size_t iter_scrub_ns; /** limit on CNAME, DNAME RRs in answer for the iterator scrubber. */ diff --git a/util/configlexer.lex b/util/configlexer.lex index 1b9eaa35b..bc258673d 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -601,6 +601,7 @@ edns-client-string{COLON} { YDVAR(2, VAR_EDNS_CLIENT_STRING) } edns-client-string-opcode{COLON} { YDVAR(1, VAR_EDNS_CLIENT_STRING_OPCODE) } nsid{COLON} { YDVAR(1, VAR_NSID ) } ede{COLON} { YDVAR(1, VAR_EDE ) } +dns-error-reporting{COLON} { YDVAR(1, VAR_DNS_ERROR_REPORTING ) } proxy-protocol-port{COLON} { YDVAR(1, VAR_PROXY_PROTOCOL_PORT) } iter-scrub-ns{COLON} { YDVAR(1, VAR_ITER_SCRUB_NS) } iter-scrub-cname{COLON} { YDVAR(1, VAR_ITER_SCRUB_CNAME) } diff --git a/util/configparser.y b/util/configparser.y index af47b0eb7..ebb23f41c 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -206,6 +206,7 @@ extern struct config_parser_state* cfg_parser; %token VAR_EDNS_CLIENT_STRING_OPCODE VAR_NSID %token VAR_ZONEMD_PERMISSIVE_MODE VAR_ZONEMD_CHECK VAR_ZONEMD_REJECT_ABSENCE %token VAR_RPZ_SIGNAL_NXDOMAIN_RA VAR_INTERFACE_AUTOMATIC_PORTS VAR_EDE +%token VAR_DNS_ERROR_REPORTING %token VAR_INTERFACE_ACTION VAR_INTERFACE_VIEW VAR_INTERFACE_TAG %token VAR_INTERFACE_TAG_ACTION VAR_INTERFACE_TAG_DATA %token VAR_QUIC_PORT VAR_QUIC_SIZE @@ -350,6 +351,7 @@ content_server: server_num_threads | server_verbosity | server_port | server_tcp_reuse_timeout | server_tcp_auth_query_timeout | server_quic_port | server_quic_size | server_interface_automatic_ports | server_ede | + server_dns_error_reporting | server_proxy_protocol_port | server_statistics_inhibit_zero | server_harden_unknown_additional | server_disable_edns_do | server_log_destaddr | server_cookie_secret_file | @@ -3073,6 +3075,15 @@ server_ede: VAR_EDE STRING_ARG free($2); } ; +server_dns_error_reporting: VAR_DNS_ERROR_REPORTING STRING_ARG + { + OUTYY(("P(server_dns_error_reporting:%s)\n", $2)); + if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0) + yyerror("expected yes or no."); + else cfg_parser->cfg->dns_error_reporting = (strcmp($2, "yes")==0); + free($2); + } + ; server_proxy_protocol_port: VAR_PROXY_PROTOCOL_PORT STRING_ARG { OUTYY(("P(server_proxy_protocol_port:%s)\n", $2)); From db6505eb8befe4698c07772254442444a48c0f99 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 7 Apr 2025 10:28:37 +0200 Subject: [PATCH 097/218] Changelog entry for #902: - Merge #902: DNS Error Reporting (RFC 9567). Introduces new configuration option 'dns-error-reporting' and new statistics for 'num.dns_error_reports'. --- doc/Changelog | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index d6fee9b08..08473be70 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,8 @@ +7 April 2025: Yorgos + - Merge #902: DNS Error Reporting (RFC 9567). Introduces new + configuration option 'dns-error-reporting' and new statistics for + 'num.dns_error_reports'. + 4 April 2025: Wouter - Fix mesh_copy_client_info to omit null contents from copy. - Fix comment name in the rpz nsdname test. From e94e140f49c2e2859c95e087bb8d79c7ebc4310b Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 7 Apr 2025 16:56:38 +0200 Subject: [PATCH 098/218] - More explicit text about memory usage during fast_reload. --- doc/unbound-control.8.in | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/unbound-control.8.in b/doc/unbound-control.8.in index 6c0cdc217..8f0d125a7 100644 --- a/doc/unbound-control.8.in +++ b/doc/unbound-control.8.in @@ -67,7 +67,16 @@ is loaded in a thread, and prepared, then it briefly pauses the existing server and updates config options. The intent is that the pause does not impact the service of user queries. The cache is kept. Also user queries worked on are kept and continue, but with the new config options. +.IP This command is experimental at this time. +.IP +The amount of temporal memory needed during a fast_reload is twice the +amount needed for configuration. +This is because Unbound temporarily needs to store both current configuration +values and new ones while trying to fast_reload. +Zones loaded from disk (authority zones and RPZ zones) are included in such +memory needs. +.IP Not all options are changed, but it changes like forwards, stubs and local zones. Also access-control and interface-action and similar options, also tcp-connection-limits, views. It can reload some define-tag changes. From 5eb1382fc08ce9098c94517fd5f50962b1051b94 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 8 Apr 2025 08:37:30 +0200 Subject: [PATCH 099/218] - Tag for 1.23.0rc1. --- configure | 26 +++++++++++++------------- configure.ac | 6 +++--- doc/Changelog | 3 +++ 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/configure b/configure index e2276279a..2225dba1f 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for unbound 1.22.1. +# Generated by GNU Autoconf 2.71 for unbound 1.23.0. # # Report bugs to . # @@ -622,8 +622,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='unbound' PACKAGE_TARNAME='unbound' -PACKAGE_VERSION='1.22.1' -PACKAGE_STRING='unbound 1.22.1' +PACKAGE_VERSION='1.23.0' +PACKAGE_STRING='unbound 1.23.0' PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues' PACKAGE_URL='' @@ -1511,7 +1511,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures unbound 1.22.1 to adapt to many kinds of systems. +\`configure' configures unbound 1.23.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1577,7 +1577,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of unbound 1.22.1:";; + short | recursive ) echo "Configuration of unbound 1.23.0:";; esac cat <<\_ACEOF @@ -1830,7 +1830,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -unbound configure 1.22.1 +unbound configure 1.23.0 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2487,7 +2487,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by unbound $as_me 1.22.1, which was +It was created by unbound $as_me 1.23.0, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3249,9 +3249,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu UNBOUND_VERSION_MAJOR=1 -UNBOUND_VERSION_MINOR=22 +UNBOUND_VERSION_MINOR=23 -UNBOUND_VERSION_MICRO=1 +UNBOUND_VERSION_MICRO=0 LIBUNBOUND_CURRENT=9 @@ -3353,7 +3353,7 @@ LIBUNBOUND_AGE=1 # 1.21.0 had 9:28:1 # 1.21.1 had 9:29:1 # 1.22.0 had 9:30:1 -# 1.22.1 had 9:31:1 +# 1.23.0 had 9:31:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary @@ -25031,7 +25031,7 @@ printf "%s\n" "#define MAXSYSLOGMSGLEN 10240" >>confdefs.h -version=1.22.1 +version=1.23.0 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5 printf %s "checking for build time... " >&6; } @@ -25561,7 +25561,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by unbound $as_me 1.22.1, which was +This file was extended by unbound $as_me 1.23.0, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -25629,7 +25629,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -unbound config.status 1.22.1 +unbound config.status 1.23.0 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 98b8241cf..2383934f8 100644 --- a/configure.ac +++ b/configure.ac @@ -11,8 +11,8 @@ sinclude(dnscrypt/dnscrypt.m4) # must be numbers. ac_defun because of later processing m4_define([VERSION_MAJOR],[1]) -m4_define([VERSION_MINOR],[22]) -m4_define([VERSION_MICRO],[1]) +m4_define([VERSION_MINOR],[23]) +m4_define([VERSION_MICRO],[0]) AC_INIT([unbound],m4_defn([VERSION_MAJOR]).m4_defn([VERSION_MINOR]).m4_defn([VERSION_MICRO]),[unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues],[unbound]) AC_SUBST(UNBOUND_VERSION_MAJOR, [VERSION_MAJOR]) AC_SUBST(UNBOUND_VERSION_MINOR, [VERSION_MINOR]) @@ -117,7 +117,7 @@ LIBUNBOUND_AGE=1 # 1.21.0 had 9:28:1 # 1.21.1 had 9:29:1 # 1.22.0 had 9:30:1 -# 1.22.1 had 9:31:1 +# 1.23.0 had 9:31:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary diff --git a/doc/Changelog b/doc/Changelog index 08473be70..7d3ea168b 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +8 April 2025: Wouter + - Tag for 1.23.0rc1. + 7 April 2025: Yorgos - Merge #902: DNS Error Reporting (RFC 9567). Introduces new configuration option 'dns-error-reporting' and new statistics for From f9d740dfdc2bf74ea225624c1b462e293bbb864e Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 8 Apr 2025 15:34:51 +0200 Subject: [PATCH 100/218] - Update to the manpage for the fast_reload part. --- doc/unbound-control.8.in | 105 +++++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 26 deletions(-) diff --git a/doc/unbound-control.8.in b/doc/unbound-control.8.in index 8f0d125a7..8adaf7d6d 100644 --- a/doc/unbound-control.8.in +++ b/doc/unbound-control.8.in @@ -77,32 +77,85 @@ values and new ones while trying to fast_reload. Zones loaded from disk (authority zones and RPZ zones) are included in such memory needs. .IP -Not all options are changed, but it changes like forwards, stubs and -local zones. Also access-control and interface-action and similar options, -also tcp-connection-limits, views. It can reload some define-tag changes. -It does not work with interface, outgoing-interface changes, also not with -remote-control, outgoing-port-permit, outgoing-port-avoid, msg-buffer-size, -slabs options and statistics-interval changes. +Options that can be changed are for +forwards, +stubs, +views, +authority zones, +RPZ zones and +local zones. .IP -The fast reload also works on the options: insecure-lan-zones, domain-insecure, -trust-anchor-file, trust-anchor, trusted-key-file, auto-trust-anchor-file, -auth-zone and its options, rpz and its options, edns-strings, respip_set, -view and its options, access-control options, tcp-connection-limit, -log-identity, infra-cache-numhosts, msg-cache-size, rrset-cache-size, -key-cache-size, ratelimit-size, neg-cache-size, num-queries-per-thread, -jostle-timeout, use-caps-for-id, unwanted-reply-threshold, tls-use-sni, -outgoing-tcp-mss, ip-dscp, max-reuse-tcp-queries, tcp-reuse-timeout, -tcp-auth-query-timeout, delay-close. +Also +access-control and similar options, +interface-action and similar options and +tcp-connection-limit. +It can reload some +define-tag +changes, more on that below. +Further options include +insecure-lan-zones, +domain-insecure, +trust-anchor-file, +trust-anchor, +trusted-keys-file, +auto-trust-anchor-file, +edns-client-string, +ipset, +log-identity, +infra-cache-numhosts, +msg-cache-size, +rrset-cache-size, +key-cache-size, +ratelimit-size, +neg-cache-size, +num-queries-per-thread, +jostle-timeout, +use-caps-for-id, +unwanted-reply-threshold, +tls-use-sni, +outgoing-tcp-mss, +ip-dscp, +max-reuse-tcp-queries, +tcp-reuse-timeout, +tcp-auth-query-timeout, +delay-close. .IP -For dnstap, the options can be changed: dnstap-log-resolver-query-messages, -dnstap-log-resolver-response-messages, dnstap-log-client-query-messages, -dnstap-log-client-response-messages, dnstap-log-forwarder-query-messages -and dnstap-log-forwarder-response-messages. It does not work with -these options: dnstap-enable, dnstap-bidirectional, dnstap-socket-path, -dnstap-ip, dnstap-tls, dnstap-tls-server-name, dnstap-tls-cert-bundle, -dnstap-tls-client-key-file and dnstap-tls-client-cert-file. The options -dnstap-send-identity, dnstap-send-version, dnstap-identity, and -dnstap-version can be loaded when '+p' is not used. +It does not work with +interface and +outgoing-interface changes, +also not with +remote control, +outgoing-port-permit, +outgoing-port-avoid, +msg-buffer-size, +any **\*-slabs** options and +statistics-interval changes. +.IP +For dnstap these options can be changed: +dnstap-log-resolver-query-messages, +dnstap-log-resolver-response-messages, +dnstap-log-client-query-messages, +dnstap-log-client-response-messages, +dnstap-log-forwarder-query-messages and +dnstap-log-forwarder-response-messages. +.IP +It does not work with these options: +dnstap-enable, +dnstap-bidirectional, +dnstap-socket-path, +dnstap-ip, +dnstap-tls, +dnstap-tls-server-name, +dnstap-tls-cert-bundle, +dnstap-tls-client-key-file and +dnstap-tls-client-cert-file. +.IP +The options +dnstap-send-identity, +dnstap-send-version, +dnstap-identity, and +dnstap-version can be loaded +when ``+p`` is not used. .IP The '+v' option makes the output verbose which includes the time it took to do the reload. @@ -128,7 +181,7 @@ worker thread. .IP With the nopause option, the reload does not work to reload some options, that fast reload works on without the nopause option: val-bogus-ttl, -val-date-override, val-sig-key-min, val-sig-skew-max, val-max-restart, +val-override-date, val-sig-skew-min, val-sig-skew-max, val-max-restart, val-nsec3-keysize-iterations, target-fetch-policy, outbound-msg-retry, max-sent-count, max-query-restarts, do-not-query-address, do-not-query-localhost, private-address, private-domain, caps-exempt, @@ -142,7 +195,7 @@ so that users keep getting answers for those queries that are currently processed. The drop makes it so that queries during the life time of the query processing see only old, or only new config options. .IP -When there are changes to the config tags, from \fBdefine\-tag\fR config, +When there are changes to the config tags, from the \fBdefine\-tag\fR option, then the '+d' option is implicitly turned on with a warning printout, and queries are dropped. This is to stop references to the old tag information, by the old From 922c5c3ab39746e2d573c8e5f0a5f1cc53900827 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 8 Apr 2025 15:42:56 +0200 Subject: [PATCH 101/218] - Fix fast_reload to print chroot with config file name. --- daemon/remote.c | 10 ++++++++-- doc/Changelog | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 50bdefd68..72c634513 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -4262,7 +4262,10 @@ fr_read_config(struct fast_reload_thread* fr, struct config_file** newcfg) if(!config_read(*newcfg, fr->worker->daemon->cfgfile, fr->worker->daemon->chroot)) { config_delete(*newcfg); - if(!fr_output_printf(fr, "config_read %s failed: %s\n", + if(!fr_output_printf(fr, "config_read %s%s%s%s failed: %s\n", + (fr->worker->daemon->chroot?"worker->daemon->chroot?fr->worker->daemon->chroot:""), + (fr->worker->daemon->chroot?"> ":""), fr->worker->daemon->cfgfile, strerror(errno))) return 0; fr_send_notification(fr, fast_reload_notification_printout); @@ -4271,7 +4274,10 @@ fr_read_config(struct fast_reload_thread* fr, struct config_file** newcfg) if(fr_poll_for_quit(fr)) return 1; if(fr->fr_verb >= 1) { - if(!fr_output_printf(fr, "done read config file %s\n", + if(!fr_output_printf(fr, "done read config file %s%s%s%s\n", + (fr->worker->daemon->chroot?"worker->daemon->chroot?fr->worker->daemon->chroot:""), + (fr->worker->daemon->chroot?"> ":""), fr->worker->daemon->cfgfile)) return 0; fr_send_notification(fr, fast_reload_notification_printout); diff --git a/doc/Changelog b/doc/Changelog index 7d3ea168b..88ee61312 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,5 +1,6 @@ 8 April 2025: Wouter - Tag for 1.23.0rc1. + - Fix fast_reload to print chroot with config file name. 7 April 2025: Yorgos - Merge #902: DNS Error Reporting (RFC 9567). Introduces new From fca3ae05354dad6e00ca89baff33b7f7d1dca602 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 9 Apr 2025 11:06:25 +0200 Subject: [PATCH 102/218] - Fix to detect if atomic_store links in configure. --- config.h.in | 3 +++ configure | 42 ++++++++++++++++++++++++++++++++++++++++++ configure.ac | 19 +++++++++++++++++++ daemon/remote.c | 10 +++++----- doc/Changelog | 3 +++ 5 files changed, 72 insertions(+), 5 deletions(-) diff --git a/config.h.in b/config.h.in index 246f06c2f..f2dc8c8b9 100644 --- a/config.h.in +++ b/config.h.in @@ -378,6 +378,9 @@ /* Define if we have LibreSSL */ #undef HAVE_LIBRESSL +/* If we have atomic_store */ +#undef HAVE_LINK_ATOMIC_STORE + /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_NET_TSTAMP_H diff --git a/configure b/configure index 2225dba1f..0b78d97b1 100755 --- a/configure +++ b/configure @@ -23530,6 +23530,48 @@ if echo $host_os | grep darwin8 > /dev/null; then printf "%s\n" "#define DARWIN_BROKEN_SETREUID 1" >>confdefs.h fi + +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for atomic_store" >&5 +printf %s "checking for atomic_store... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_includes_default +#ifdef HAVE_STDATOMIC_H +#include +#endif + +int +main (void) +{ + + int newvar = 5, var = 0; + atomic_store((_Atomic int*)&var, newvar); + newvar = 0; + /* condition to use the variables. */ + if(var == newvar) return 1; + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } + +printf "%s\n" "#define HAVE_LINK_ATOMIC_STORE 1" >>confdefs.h + + +else $as_nop + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } + +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext + ac_fn_check_decl "$LINENO" "inet_pton" "ac_cv_have_decl_inet_pton" " $ac_includes_default #ifdef HAVE_NETINET_IN_H diff --git a/configure.ac b/configure.ac index 2383934f8..76239c099 100644 --- a/configure.ac +++ b/configure.ac @@ -1818,6 +1818,25 @@ AC_CHECK_FUNCS([setresgid],,[AC_CHECK_FUNCS([setregid])]) if echo $host_os | grep darwin8 > /dev/null; then AC_DEFINE(DARWIN_BROKEN_SETREUID, 1, [Define this if on macOSX10.4-darwin8 and setreuid and setregid do not work]) fi + +AC_MSG_CHECKING([for atomic_store]) +AC_LINK_IFELSE([AC_LANG_PROGRAM(AC_INCLUDES_DEFAULT [[ +#ifdef HAVE_STDATOMIC_H +#include +#endif +]], [[ + int newvar = 5, var = 0; + atomic_store((_Atomic int*)&var, newvar); + newvar = 0; + /* condition to use the variables. */ + if(var == newvar) return 1; +]])], [ + AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_LINK_ATOMIC_STORE, 1, [If we have atomic_store]) +], [ + AC_MSG_RESULT([no]) +]) + AC_CHECK_DECLS([inet_pton,inet_ntop], [], [], [ AC_INCLUDES_DEFAULT #ifdef HAVE_NETINET_IN_H diff --git a/daemon/remote.c b/daemon/remote.c index 72c634513..3cf795a69 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -5450,7 +5450,7 @@ auth_zones_swap(struct auth_zones* az, struct auth_zones* data) * the xfer elements can continue to be their callbacks. */ } -#ifdef ATOMIC_POINTER_LOCK_FREE +#if defined(ATOMIC_POINTER_LOCK_FREE) && defined(HAVE_LINK_ATOMIC_STORE) /** Fast reload thread, if atomics are available, copy the config items * one by one with atomic store operations. */ static void @@ -5819,7 +5819,7 @@ fr_atomic_copy_cfg(struct config_file* oldcfg, struct config_file* cfg, #endif COPY_VAR_int(ede); } -#endif /* ATOMIC_POINTER_LOCK_FREE */ +#endif /* ATOMIC_POINTER_LOCK_FREE && HAVE_LINK_ATOMIC_STORE */ /** fast reload thread, adjust the cache sizes */ static void @@ -5997,7 +5997,7 @@ fr_reload_config(struct fast_reload_thread* fr, struct config_file* newcfg, lock_basic_lock(&env->anchors->lock); } -#ifdef ATOMIC_POINTER_LOCK_FREE +#if defined(ATOMIC_POINTER_LOCK_FREE) && defined(HAVE_LINK_ATOMIC_STORE) if(fr->fr_nopause) { fr_atomic_copy_cfg(ct->oldcfg, env->cfg, newcfg); } else { @@ -6006,7 +6006,7 @@ fr_reload_config(struct fast_reload_thread* fr, struct config_file* newcfg, *ct->oldcfg = *env->cfg; /* Insert new config elements. */ *env->cfg = *newcfg; -#ifdef ATOMIC_POINTER_LOCK_FREE +#if defined(ATOMIC_POINTER_LOCK_FREE) && defined(HAVE_LINK_ATOMIC_STORE) } #endif @@ -6015,7 +6015,7 @@ fr_reload_config(struct fast_reload_thread* fr, struct config_file* newcfg, log_ident_set_or_default(env->cfg->log_identity); } /* the newcfg elements are in env->cfg, so should not be freed here. */ -#ifdef ATOMIC_POINTER_LOCK_FREE +#if defined(ATOMIC_POINTER_LOCK_FREE) && defined(HAVE_LINK_ATOMIC_STORE) /* if used, the routine that copies the config has zeroed items. */ if(!fr->fr_nopause) #endif diff --git a/doc/Changelog b/doc/Changelog index 88ee61312..d37b0cd02 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +9 April 2025: Wouter + - Fix to detect if atomic_store links in configure. + 8 April 2025: Wouter - Tag for 1.23.0rc1. - Fix fast_reload to print chroot with config file name. From 4f06e658d1a10a2782a475e76cb0c4d308e08f7c Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 9 Apr 2025 14:13:58 +0200 Subject: [PATCH 103/218] - Fix #1264: unbound 1.22.0 leaks memory when doing DoH. --- doc/Changelog | 3 ++ util/netevent.c | 82 ++++++++++++++++++++++++++++--------------------- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 7d3ea168b..2276329e5 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +9 April 2025: Wouter + - Fix #1264: unbound 1.22.0 leaks memory when doing DoH. + 8 April 2025: Wouter - Tag for 1.23.0rc1. diff --git a/util/netevent.c b/util/netevent.c index da8430e41..f9dff1f9a 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -3057,7 +3057,7 @@ int comm_point_perform_accept(struct comm_point* c, if(verbosity >= 3) log_err_addr("accept rejected", "connection limit exceeded", addr, *addrlen); - close(new_fd); + sock_close(new_fd); return -1; } } @@ -3158,6 +3158,40 @@ static int http2_submit_settings(struct http2_session* h2_session) } #endif /* HAVE_NGHTTP2 */ +#ifdef HAVE_NGHTTP2 +/** Delete http2 stream. After session delete or stream close callback */ +static void http2_stream_delete(struct http2_session* h2_session, + struct http2_stream* h2_stream) +{ + if(h2_stream->mesh_state) { + mesh_state_remove_reply(h2_stream->mesh, h2_stream->mesh_state, + h2_session->c); + h2_stream->mesh_state = NULL; + } + http2_req_stream_clear(h2_stream); + free(h2_stream); +} +#endif /* HAVE_NGHTTP2 */ + +/** delete http2 session server. After closing connection. */ +static void http2_session_server_delete(struct http2_session* h2_session) +{ +#ifdef HAVE_NGHTTP2 + struct http2_stream* h2_stream, *next; + nghttp2_session_del(h2_session->session); /* NULL input is fine */ + h2_session->session = NULL; + for(h2_stream = h2_session->first_stream; h2_stream;) { + next = h2_stream->next; + http2_stream_delete(h2_session, h2_stream); + h2_stream = next; + } + h2_session->first_stream = NULL; + h2_session->is_drop = 0; + h2_session->postpone_drop = 0; + h2_session->c->h2_stream = NULL; +#endif + (void)h2_session; +} void comm_point_tcp_accept_callback(int fd, short event, void* arg) @@ -3196,6 +3230,8 @@ comm_point_tcp_accept_callback(int fd, short event, void* arg) if(!c_hdl->h2_session || !http2_submit_settings(c_hdl->h2_session)) { log_warn("failed to submit http2 settings"); + if(c_hdl->h2_session) + http2_session_server_delete(c_hdl->h2_session); return; } if(!c->ssl) { @@ -3213,14 +3249,23 @@ comm_point_tcp_accept_callback(int fd, short event, void* arg) } if(!c_hdl->ev->ev) { log_warn("could not ub_event_new, dropped tcp"); +#ifdef HAVE_NGHTTP2 + if(c_hdl->type == comm_http && c_hdl->h2_session) + http2_session_server_delete(c_hdl->h2_session); +#endif return; } log_assert(fd != -1); (void)fd; new_fd = comm_point_perform_accept(c, &c_hdl->repinfo.remote_addr, &c_hdl->repinfo.remote_addrlen); - if(new_fd == -1) + if(new_fd == -1) { +#ifdef HAVE_NGHTTP2 + if(c_hdl->type == comm_http && c_hdl->h2_session) + http2_session_server_delete(c_hdl->h2_session); +#endif return; + } /* Copy remote_address to client_address. * Simplest way/time for streams to do that. */ c_hdl->repinfo.client_addrlen = c_hdl->repinfo.remote_addrlen; @@ -5035,19 +5080,6 @@ struct http2_stream* http2_stream_create(int32_t stream_id) h2_stream->stream_id = stream_id; return h2_stream; } - -/** Delete http2 stream. After session delete or stream close callback */ -static void http2_stream_delete(struct http2_session* h2_session, - struct http2_stream* h2_stream) -{ - if(h2_stream->mesh_state) { - mesh_state_remove_reply(h2_stream->mesh, h2_stream->mesh_state, - h2_session->c); - h2_stream->mesh_state = NULL; - } - http2_req_stream_clear(h2_stream); - free(h2_stream); -} #endif void http2_stream_add_meshstate(struct http2_stream* h2_stream, @@ -5064,26 +5096,6 @@ void http2_stream_remove_mesh_state(struct http2_stream* h2_stream) h2_stream->mesh_state = NULL; } -/** delete http2 session server. After closing connection. */ -static void http2_session_server_delete(struct http2_session* h2_session) -{ -#ifdef HAVE_NGHTTP2 - struct http2_stream* h2_stream, *next; - nghttp2_session_del(h2_session->session); /* NULL input is fine */ - h2_session->session = NULL; - for(h2_stream = h2_session->first_stream; h2_stream;) { - next = h2_stream->next; - http2_stream_delete(h2_session, h2_stream); - h2_stream = next; - } - h2_session->first_stream = NULL; - h2_session->is_drop = 0; - h2_session->postpone_drop = 0; - h2_session->c->h2_stream = NULL; -#endif - (void)h2_session; -} - #ifdef HAVE_NGHTTP2 void http2_session_add_stream(struct http2_session* h2_session, struct http2_stream* h2_stream) From 16ee7cf944aac2acf53baba31e71f4b44cb7bb58 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 10 Apr 2025 09:33:51 +0200 Subject: [PATCH 104/218] - Fix for print of connection type in log-replies for dot and doh. --- daemon/worker.c | 4 ++-- doc/Changelog | 3 +++ services/mesh.c | 2 +- util/data/msgreply.c | 8 ++++---- util/data/msgreply.h | 4 +++- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/daemon/worker.c b/daemon/worker.c index 54ba2d76c..ead20938e 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -2055,13 +2055,13 @@ send_reply_rc: &repinfo->client_addr, repinfo->client_addrlen, tv, 1, c->buffer, (worker->env.cfg->log_destaddr?(void*)repinfo->c->socket->addr:NULL), - c->type); + c->type, c->ssl); } else { log_reply_info(NO_VERBOSE, &qinfo, &repinfo->client_addr, repinfo->client_addrlen, tv, 1, c->buffer, (worker->env.cfg->log_destaddr?(void*)repinfo->c->socket->addr:NULL), - c->type); + c->type, c->ssl); } } #ifdef USE_DNSCRYPT diff --git a/doc/Changelog b/doc/Changelog index 08f6c09f4..f8e2a7000 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +10 April 2025: Wouter + - Fix for print of connection type in log-replies for dot and doh. + 9 April 2025: Wouter - Fix to detect if atomic_store links in configure. - Fix #1264: unbound 1.22.0 leaks memory when doing DoH. diff --git a/services/mesh.c b/services/mesh.c index 1d19e7c7d..8a52fe4a6 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -1579,7 +1579,7 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, &r->query_reply.client_addr, r->query_reply.client_addrlen, duration, 0, r_buffer, (m->s.env->cfg->log_destaddr?(void*)r->query_reply.c->socket->addr:NULL), - r->query_reply.c->type); + r->query_reply.c->type, r->query_reply.c->ssl); } } diff --git a/util/data/msgreply.c b/util/data/msgreply.c index 530eee701..e98dce133 100644 --- a/util/data/msgreply.c +++ b/util/data/msgreply.c @@ -965,7 +965,7 @@ void log_reply_info(enum verbosity_value v, struct query_info *qinf, struct sockaddr_storage *addr, socklen_t addrlen, struct timeval dur, int cached, struct sldns_buffer *rmsg, struct sockaddr_storage* daddr, - enum comm_point_type tp) + enum comm_point_type tp, void* ssl) { char clientip_buf[128]; char rcode_buf[16]; @@ -1000,9 +1000,9 @@ log_reply_info(enum verbosity_value v, struct query_info *qinf, (int)daddr->ss_family); } comm = "udp"; - if(tp == comm_tcp) comm = "tcp"; - else if(tp == comm_tcp_accept) comm = "tcp"; - else if(tp == comm_http) comm = "dot"; + if(tp == comm_tcp) comm = (ssl?"dot":"tcp"); + else if(tp == comm_tcp_accept) comm = (ssl?"dot":"tcp"); + else if(tp == comm_http) comm = "doh"; else if(tp == comm_local) comm = "unix"; else if(tp == comm_raw) comm = "raw"; snprintf(dest_buf, sizeof(dest_buf), " on %s %s %d", diff --git a/util/data/msgreply.h b/util/data/msgreply.h index d1c2bfa4b..9c701f07d 100644 --- a/util/data/msgreply.h +++ b/util/data/msgreply.h @@ -554,11 +554,13 @@ void log_dns_msg(const char* str, struct query_info* qinfo, * @param rmsg: sldns buffer packet. * @param daddr: if not NULL, the destination address and port are logged. * @param tp: type of the comm point for logging destination connection type. + * @param ssl: the SSL pointer of the connection, to see if the connection + * type is tcp or dot. */ void log_reply_info(enum verbosity_value v, struct query_info *qinf, struct sockaddr_storage *addr, socklen_t addrlen, struct timeval dur, int cached, struct sldns_buffer *rmsg, struct sockaddr_storage* daddr, - enum comm_point_type tp); + enum comm_point_type tp, void* ssl); /** * Print string with neat domain name, type, class from query info. From 75e8fd75391f7575e50610773eea1460c366561c Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 11 Apr 2025 15:05:52 +0200 Subject: [PATCH 105/218] Fix WSAPoll (#1265) * Fix calling WSAPoll. * fast_reload: explicitly set tcp_wouldblock on Windows when there is no command to read from the fast_reload thread. * For poll(), also check for ENOMEM (Linux). * Remove ifdefs for ENOMEM. * Some systems return EAGAIN for poll. --- daemon/remote.c | 45 +++++++++++++++++++++++++++---------- util/netevent.c | 60 +++++++++++++++++++++++++------------------------ 2 files changed, 64 insertions(+), 41 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 50bdefd68..ea6f3d809 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -3899,6 +3899,7 @@ sock_poll_timeout(int fd, int timeout, int pollin, int pollout, int* event) { int loopcount = 0; /* Loop if the system call returns an errno to do so, like EINTR. */ + log_assert(pollin || pollout); while(1) { struct pollfd p, *fds; int nfds, ret; @@ -3916,11 +3917,11 @@ sock_poll_timeout(int fd, int timeout, int pollin, int pollout, int* event) nfds = 1; memset(&p, 0, sizeof(p)); p.fd = fd; - p.events = POLLERR #ifndef USE_WINSOCK + p.events = POLLERR | POLLHUP -#endif ; +#endif if(pollin) p.events |= POLLIN; if(pollout) @@ -3937,19 +3938,20 @@ sock_poll_timeout(int fd, int timeout, int pollin, int pollout, int* event) } #endif if(ret == -1) { - if( #ifndef USE_WINSOCK + if( errno == EINTR || errno == EAGAIN # ifdef EWOULDBLOCK || errno == EWOULDBLOCK # endif -#else - WSAGetLastError() == WSAEINTR || - WSAGetLastError() == WSAEINPROGRESS || - WSAGetLastError() == WSAEWOULDBLOCK + ) continue; /* Try again. */ #endif - ) - continue; /* Try again. */ + /* For WSAPoll we only get errors here: + * o WSAENETDOWN + * o WSAEFAULT + * o WSAEINVAL + * o WSAENOBUFS + */ log_err("poll: %s", sock_strerror(errno)); if(event) *event = 0; @@ -7392,11 +7394,17 @@ fr_main_handle_cmd(struct fast_reload_thread* fr) # endif #else WSAGetLastError() == WSAEINTR || - WSAGetLastError() == WSAEINPROGRESS || - WSAGetLastError() == WSAEWOULDBLOCK + WSAGetLastError() == WSAEINPROGRESS #endif ) return; /* Continue later. */ +#ifdef USE_WINSOCK + if(WSAGetLastError() == WSAEWOULDBLOCK) { + ub_winsock_tcp_wouldblock(fr->service_event, + UB_EV_READ); + return; /* Continue later. */ + } +#endif log_err("read cmd from fast reload thread, recv: %s", sock_strerror(errno)); return; @@ -7428,10 +7436,23 @@ fr_check_cmd_from_thread(struct fast_reload_thread* fr) if(!sock_poll_timeout(fr->commpair[0], 0, 1, 0, &inevent)) { log_err("check for cmd from fast reload thread: " "poll failed"); +#ifdef USE_WINSOCK + if(worker->daemon->fast_reload_thread) + ub_winsock_tcp_wouldblock(worker->daemon-> + fast_reload_thread->service_event, + UB_EV_READ); +#endif return; } - if(!inevent) + if(!inevent) { +#ifdef USE_WINSOCK + if(worker->daemon->fast_reload_thread) + ub_winsock_tcp_wouldblock(worker->daemon-> + fast_reload_thread->service_event, + UB_EV_READ); +#endif return; + } fr_main_handle_cmd(fr); } } diff --git a/util/netevent.c b/util/netevent.c index f9dff1f9a..0d0fff429 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -456,9 +456,9 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet, int pret; memset(&p, 0, sizeof(p)); p.fd = c->fd; - p.events = POLLOUT | POLLERR + p.events = POLLOUT #ifndef USE_WINSOCK - | POLLHUP + | POLLERR | POLLHUP #endif ; # ifndef USE_WINSOCK @@ -483,7 +483,7 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet, # ifdef EWOULDBLOCK errno != EWOULDBLOCK && # endif - errno != ENOBUFS + errno != ENOMEM && errno != ENOBUFS #else WSAGetLastError() != WSAEINPROGRESS && WSAGetLastError() != WSAEINTR && @@ -496,15 +496,19 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet, return 0; } else if((pret < 0 && #ifndef USE_WINSOCK - errno == ENOBUFS + ( errno == ENOBUFS /* Maybe some systems */ + || errno == ENOMEM /* Linux */ + || errno == EAGAIN) /* Macos, solaris, openbsd */ #else WSAGetLastError() == WSAENOBUFS #endif ) || (send_nobufs && retries > 0)) { - /* ENOBUFS, and poll returned without + /* ENOBUFS/ENOMEM/EAGAIN, and poll + * returned without * a timeout. Or the retried send call - * returned ENOBUFS. It is good to - * wait a bit for the error to clear. */ + * returned ENOBUFS/ENOMEM/EAGAIN. + * It is good to wait a bit for the + * error to clear. */ /* The timeout is 20*(2^(retries+1)), * it increases exponentially, starting * at 40 msec. After 5 tries, 1240 msec @@ -517,18 +521,15 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet, Sleep((SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1)); pret = 0; #endif - if(pret < 0 && + if(pret < 0 #ifndef USE_WINSOCK - errno != EAGAIN && errno != EINTR && + && errno != EAGAIN && errno != EINTR && # ifdef EWOULDBLOCK errno != EWOULDBLOCK && # endif - errno != ENOBUFS + errno != ENOMEM && errno != ENOBUFS #else - WSAGetLastError() != WSAEINPROGRESS && - WSAGetLastError() != WSAEINTR && - WSAGetLastError() != WSAENOBUFS && - WSAGetLastError() != WSAEWOULDBLOCK + /* Sleep does not error */ #endif ) { log_err("poll udp out timer failed: %s", @@ -770,9 +771,9 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet, int pret; memset(&p, 0, sizeof(p)); p.fd = c->fd; - p.events = POLLOUT | POLLERR + p.events = POLLOUT #ifndef USE_WINSOCK - | POLLHUP + | POLLERR | POLLHUP #endif ; # ifndef USE_WINSOCK @@ -797,7 +798,7 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet, # ifdef EWOULDBLOCK errno != EWOULDBLOCK && # endif - errno != ENOBUFS + errno != ENOMEM && errno != ENOBUFS #else WSAGetLastError() != WSAEINPROGRESS && WSAGetLastError() != WSAEINTR && @@ -810,15 +811,19 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet, return 0; } else if((pret < 0 && #ifndef USE_WINSOCK - errno == ENOBUFS + ( errno == ENOBUFS /* Maybe some systems */ + || errno == ENOMEM /* Linux */ + || errno == EAGAIN) /* Macos, solaris, openbsd */ #else WSAGetLastError() == WSAENOBUFS #endif ) || (send_nobufs && retries > 0)) { - /* ENOBUFS, and poll returned without + /* ENOBUFS/ENOMEM/EAGAIN, and poll + * returned without * a timeout. Or the retried send call - * returned ENOBUFS. It is good to - * wait a bit for the error to clear. */ + * returned ENOBUFS/ENOMEM/EAGAIN. + * It is good to wait a bit for the + * error to clear. */ /* The timeout is 20*(2^(retries+1)), * it increases exponentially, starting * at 40 msec. After 5 tries, 1240 msec @@ -831,18 +836,15 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet, Sleep((SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1)); pret = 0; #endif - if(pret < 0 && + if(pret < 0 #ifndef USE_WINSOCK - errno != EAGAIN && errno != EINTR && + && errno != EAGAIN && errno != EINTR && # ifdef EWOULDBLOCK errno != EWOULDBLOCK && # endif - errno != ENOBUFS -#else - WSAGetLastError() != WSAEINPROGRESS && - WSAGetLastError() != WSAEINTR && - WSAGetLastError() != WSAENOBUFS && - WSAGetLastError() != WSAEWOULDBLOCK + errno != ENOMEM && errno != ENOBUFS +#else /* USE_WINSOCK */ + /* Sleep does not error */ #endif ) { log_err("poll udp out timer failed: %s", From 9c99b404a1039470f60949fb3373459e157d672f Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 11 Apr 2025 15:07:25 +0200 Subject: [PATCH 106/218] Changelog entry for #1265: - Merge #1265: Fix WSAPoll. --- doc/Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 2276329e5..f695d7f4e 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +11 April 2025: Yorgos + - Merge #1265: Fix WSAPoll. + 9 April 2025: Wouter - Fix #1264: unbound 1.22.0 leaks memory when doing DoH. From d6e8ac12890d4f8b18a0b9bbdf90da7f0d3a82c1 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 11 Apr 2025 15:05:52 +0200 Subject: [PATCH 107/218] Fix WSAPoll (#1265) * Fix calling WSAPoll. * fast_reload: explicitly set tcp_wouldblock on Windows when there is no command to read from the fast_reload thread. * For poll(), also check for ENOMEM (Linux). * Remove ifdefs for ENOMEM. * Some systems return EAGAIN for poll. --- daemon/remote.c | 45 +++++++++++++++++++++++++++---------- util/netevent.c | 60 +++++++++++++++++++++++++------------------------ 2 files changed, 64 insertions(+), 41 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 3cf795a69..89134efc9 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -3899,6 +3899,7 @@ sock_poll_timeout(int fd, int timeout, int pollin, int pollout, int* event) { int loopcount = 0; /* Loop if the system call returns an errno to do so, like EINTR. */ + log_assert(pollin || pollout); while(1) { struct pollfd p, *fds; int nfds, ret; @@ -3916,11 +3917,11 @@ sock_poll_timeout(int fd, int timeout, int pollin, int pollout, int* event) nfds = 1; memset(&p, 0, sizeof(p)); p.fd = fd; - p.events = POLLERR #ifndef USE_WINSOCK + p.events = POLLERR | POLLHUP -#endif ; +#endif if(pollin) p.events |= POLLIN; if(pollout) @@ -3937,19 +3938,20 @@ sock_poll_timeout(int fd, int timeout, int pollin, int pollout, int* event) } #endif if(ret == -1) { - if( #ifndef USE_WINSOCK + if( errno == EINTR || errno == EAGAIN # ifdef EWOULDBLOCK || errno == EWOULDBLOCK # endif -#else - WSAGetLastError() == WSAEINTR || - WSAGetLastError() == WSAEINPROGRESS || - WSAGetLastError() == WSAEWOULDBLOCK + ) continue; /* Try again. */ #endif - ) - continue; /* Try again. */ + /* For WSAPoll we only get errors here: + * o WSAENETDOWN + * o WSAEFAULT + * o WSAEINVAL + * o WSAENOBUFS + */ log_err("poll: %s", sock_strerror(errno)); if(event) *event = 0; @@ -7398,11 +7400,17 @@ fr_main_handle_cmd(struct fast_reload_thread* fr) # endif #else WSAGetLastError() == WSAEINTR || - WSAGetLastError() == WSAEINPROGRESS || - WSAGetLastError() == WSAEWOULDBLOCK + WSAGetLastError() == WSAEINPROGRESS #endif ) return; /* Continue later. */ +#ifdef USE_WINSOCK + if(WSAGetLastError() == WSAEWOULDBLOCK) { + ub_winsock_tcp_wouldblock(fr->service_event, + UB_EV_READ); + return; /* Continue later. */ + } +#endif log_err("read cmd from fast reload thread, recv: %s", sock_strerror(errno)); return; @@ -7434,10 +7442,23 @@ fr_check_cmd_from_thread(struct fast_reload_thread* fr) if(!sock_poll_timeout(fr->commpair[0], 0, 1, 0, &inevent)) { log_err("check for cmd from fast reload thread: " "poll failed"); +#ifdef USE_WINSOCK + if(worker->daemon->fast_reload_thread) + ub_winsock_tcp_wouldblock(worker->daemon-> + fast_reload_thread->service_event, + UB_EV_READ); +#endif return; } - if(!inevent) + if(!inevent) { +#ifdef USE_WINSOCK + if(worker->daemon->fast_reload_thread) + ub_winsock_tcp_wouldblock(worker->daemon-> + fast_reload_thread->service_event, + UB_EV_READ); +#endif return; + } fr_main_handle_cmd(fr); } } diff --git a/util/netevent.c b/util/netevent.c index f9dff1f9a..0d0fff429 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -456,9 +456,9 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet, int pret; memset(&p, 0, sizeof(p)); p.fd = c->fd; - p.events = POLLOUT | POLLERR + p.events = POLLOUT #ifndef USE_WINSOCK - | POLLHUP + | POLLERR | POLLHUP #endif ; # ifndef USE_WINSOCK @@ -483,7 +483,7 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet, # ifdef EWOULDBLOCK errno != EWOULDBLOCK && # endif - errno != ENOBUFS + errno != ENOMEM && errno != ENOBUFS #else WSAGetLastError() != WSAEINPROGRESS && WSAGetLastError() != WSAEINTR && @@ -496,15 +496,19 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet, return 0; } else if((pret < 0 && #ifndef USE_WINSOCK - errno == ENOBUFS + ( errno == ENOBUFS /* Maybe some systems */ + || errno == ENOMEM /* Linux */ + || errno == EAGAIN) /* Macos, solaris, openbsd */ #else WSAGetLastError() == WSAENOBUFS #endif ) || (send_nobufs && retries > 0)) { - /* ENOBUFS, and poll returned without + /* ENOBUFS/ENOMEM/EAGAIN, and poll + * returned without * a timeout. Or the retried send call - * returned ENOBUFS. It is good to - * wait a bit for the error to clear. */ + * returned ENOBUFS/ENOMEM/EAGAIN. + * It is good to wait a bit for the + * error to clear. */ /* The timeout is 20*(2^(retries+1)), * it increases exponentially, starting * at 40 msec. After 5 tries, 1240 msec @@ -517,18 +521,15 @@ comm_point_send_udp_msg(struct comm_point *c, sldns_buffer* packet, Sleep((SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1)); pret = 0; #endif - if(pret < 0 && + if(pret < 0 #ifndef USE_WINSOCK - errno != EAGAIN && errno != EINTR && + && errno != EAGAIN && errno != EINTR && # ifdef EWOULDBLOCK errno != EWOULDBLOCK && # endif - errno != ENOBUFS + errno != ENOMEM && errno != ENOBUFS #else - WSAGetLastError() != WSAEINPROGRESS && - WSAGetLastError() != WSAEINTR && - WSAGetLastError() != WSAENOBUFS && - WSAGetLastError() != WSAEWOULDBLOCK + /* Sleep does not error */ #endif ) { log_err("poll udp out timer failed: %s", @@ -770,9 +771,9 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet, int pret; memset(&p, 0, sizeof(p)); p.fd = c->fd; - p.events = POLLOUT | POLLERR + p.events = POLLOUT #ifndef USE_WINSOCK - | POLLHUP + | POLLERR | POLLHUP #endif ; # ifndef USE_WINSOCK @@ -797,7 +798,7 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet, # ifdef EWOULDBLOCK errno != EWOULDBLOCK && # endif - errno != ENOBUFS + errno != ENOMEM && errno != ENOBUFS #else WSAGetLastError() != WSAEINPROGRESS && WSAGetLastError() != WSAEINTR && @@ -810,15 +811,19 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet, return 0; } else if((pret < 0 && #ifndef USE_WINSOCK - errno == ENOBUFS + ( errno == ENOBUFS /* Maybe some systems */ + || errno == ENOMEM /* Linux */ + || errno == EAGAIN) /* Macos, solaris, openbsd */ #else WSAGetLastError() == WSAENOBUFS #endif ) || (send_nobufs && retries > 0)) { - /* ENOBUFS, and poll returned without + /* ENOBUFS/ENOMEM/EAGAIN, and poll + * returned without * a timeout. Or the retried send call - * returned ENOBUFS. It is good to - * wait a bit for the error to clear. */ + * returned ENOBUFS/ENOMEM/EAGAIN. + * It is good to wait a bit for the + * error to clear. */ /* The timeout is 20*(2^(retries+1)), * it increases exponentially, starting * at 40 msec. After 5 tries, 1240 msec @@ -831,18 +836,15 @@ comm_point_send_udp_msg_if(struct comm_point *c, sldns_buffer* packet, Sleep((SEND_BLOCKED_WAIT_TIMEOUT/10)<<(retries+1)); pret = 0; #endif - if(pret < 0 && + if(pret < 0 #ifndef USE_WINSOCK - errno != EAGAIN && errno != EINTR && + && errno != EAGAIN && errno != EINTR && # ifdef EWOULDBLOCK errno != EWOULDBLOCK && # endif - errno != ENOBUFS -#else - WSAGetLastError() != WSAEINPROGRESS && - WSAGetLastError() != WSAEINTR && - WSAGetLastError() != WSAENOBUFS && - WSAGetLastError() != WSAEWOULDBLOCK + errno != ENOMEM && errno != ENOBUFS +#else /* USE_WINSOCK */ + /* Sleep does not error */ #endif ) { log_err("poll udp out timer failed: %s", From 30c13d0351abd2edc3d6dc76365f576c87b9736e Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Fri, 11 Apr 2025 15:07:25 +0200 Subject: [PATCH 108/218] Changelog entry for #1265: - Merge #1265: Fix WSAPoll. --- doc/Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index f8e2a7000..9668a6364 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +11 April 2025: Yorgos + - Merge #1265: Fix WSAPoll. + 10 April 2025: Wouter - Fix for print of connection type in log-replies for dot and doh. From fe835f9d529cefb71c5720a41a69f21097696d51 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 16 Apr 2025 12:03:08 +0200 Subject: [PATCH 109/218] - Increase default to `num-queries-per-thread: 2048`, when unbound is compiled with libevent. It makes saturation of the task queue more resource intensive and less practical. Thanks to Shiming Liu, Network and Information Security Lab, Tsinghua University for the report. --- doc/Changelog | 7 +++++++ doc/example.conf.in | 2 +- doc/unbound.conf.5.in | 4 ++-- util/config_file.c | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index f695d7f4e..488b107c0 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,10 @@ +16 April 2025: Wouter + - Increase default to `num-queries-per-thread: 2048`, when unbound is + compiled with libevent. It makes saturation of the task queue more + resource intensive and less practical. Thanks to Shiming Liu, + Network and Information Security Lab, Tsinghua University for the + report. + 11 April 2025: Yorgos - Merge #1265: Fix WSAPoll. diff --git a/doc/example.conf.in b/doc/example.conf.in index fdef9ef37..db54f9739 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -163,7 +163,7 @@ server: # msg-cache-slabs: 4 # the number of queries that a thread gets to service. - # num-queries-per-thread: 1024 + # num-queries-per-thread: 2048 # if very busy, 50% queries run to completion, 50% get timeout in msec # jostle-timeout: 200 diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 21dbd73e6..4c7ea9f75 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -268,7 +268,7 @@ The number of queries that every thread will service simultaneously. If more queries arrive that need servicing, and no queries can be jostled out (see \fIjostle\-timeout\fR), then the queries are dropped. This forces the client to resend after a timeout; allowing the server time to work on -the existing queries. Default depends on compile options, 512 or 1024. +the existing queries. Default depends on compile options, 512 or 2048. .TP .B jostle\-timeout: \fI Timeout used when the server is very busy. Set to a value that usually @@ -280,7 +280,7 @@ service by slow queries or high query rates. Default 200 milliseconds. The effect is that the qps for long-lasting queries is about (numqueriesperthread / 2) / (average time for such long queries) qps. The qps for short queries can be about (numqueriesperthread / 2) -/ (jostletimeout in whole seconds) qps per thread, about (1024/2)*5 = 2560 +/ (jostletimeout in whole seconds) qps per thread, about (2048/2)*5 = 5120 qps by default. .TP .B delay\-close: \fI diff --git a/util/config_file.c b/util/config_file.c index 81bffa8d8..7566d690c 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -155,7 +155,7 @@ config_create(void) # else /* libevent can use many sockets */ cfg->outgoing_num_ports = 4096; - cfg->num_queries_per_thread = 1024; + cfg->num_queries_per_thread = 2048; # endif cfg->outgoing_num_tcp = 10; cfg->incoming_num_tcp = 10; From e794234ac8c7ab67ee53f85b55292be1554b5a77 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 24 Apr 2025 10:17:45 +0200 Subject: [PATCH 110/218] - Tag for 1.23.0rc2. This became the release of 1.23.0 on 24 April 2025. The code repository continues with 1.23.1 in development. --- configure | 25 +++++++++++++------------ configure.ac | 5 +++-- doc/Changelog | 4 ++++ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/configure b/configure index 0b78d97b1..ac1a22790 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for unbound 1.23.0. +# Generated by GNU Autoconf 2.71 for unbound 1.23.1. # # Report bugs to . # @@ -622,8 +622,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='unbound' PACKAGE_TARNAME='unbound' -PACKAGE_VERSION='1.23.0' -PACKAGE_STRING='unbound 1.23.0' +PACKAGE_VERSION='1.23.1' +PACKAGE_STRING='unbound 1.23.1' PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues' PACKAGE_URL='' @@ -1511,7 +1511,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures unbound 1.23.0 to adapt to many kinds of systems. +\`configure' configures unbound 1.23.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1577,7 +1577,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of unbound 1.23.0:";; + short | recursive ) echo "Configuration of unbound 1.23.1:";; esac cat <<\_ACEOF @@ -1830,7 +1830,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -unbound configure 1.23.0 +unbound configure 1.23.1 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2487,7 +2487,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by unbound $as_me 1.23.0, which was +It was created by unbound $as_me 1.23.1, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3251,11 +3251,11 @@ UNBOUND_VERSION_MAJOR=1 UNBOUND_VERSION_MINOR=23 -UNBOUND_VERSION_MICRO=0 +UNBOUND_VERSION_MICRO=1 LIBUNBOUND_CURRENT=9 -LIBUNBOUND_REVISION=31 +LIBUNBOUND_REVISION=32 LIBUNBOUND_AGE=1 # 1.0.0 had 0:12:0 # 1.0.1 had 0:13:0 @@ -3354,6 +3354,7 @@ LIBUNBOUND_AGE=1 # 1.21.1 had 9:29:1 # 1.22.0 had 9:30:1 # 1.23.0 had 9:31:1 +# 1.23.1 had 9:32:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary @@ -25073,7 +25074,7 @@ printf "%s\n" "#define MAXSYSLOGMSGLEN 10240" >>confdefs.h -version=1.23.0 +version=1.23.1 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5 printf %s "checking for build time... " >&6; } @@ -25603,7 +25604,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by unbound $as_me 1.23.0, which was +This file was extended by unbound $as_me 1.23.1, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -25671,7 +25672,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -unbound config.status 1.23.0 +unbound config.status 1.23.1 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 76239c099..051e7b392 100644 --- a/configure.ac +++ b/configure.ac @@ -12,14 +12,14 @@ sinclude(dnscrypt/dnscrypt.m4) # must be numbers. ac_defun because of later processing m4_define([VERSION_MAJOR],[1]) m4_define([VERSION_MINOR],[23]) -m4_define([VERSION_MICRO],[0]) +m4_define([VERSION_MICRO],[1]) AC_INIT([unbound],m4_defn([VERSION_MAJOR]).m4_defn([VERSION_MINOR]).m4_defn([VERSION_MICRO]),[unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues],[unbound]) AC_SUBST(UNBOUND_VERSION_MAJOR, [VERSION_MAJOR]) AC_SUBST(UNBOUND_VERSION_MINOR, [VERSION_MINOR]) AC_SUBST(UNBOUND_VERSION_MICRO, [VERSION_MICRO]) LIBUNBOUND_CURRENT=9 -LIBUNBOUND_REVISION=31 +LIBUNBOUND_REVISION=32 LIBUNBOUND_AGE=1 # 1.0.0 had 0:12:0 # 1.0.1 had 0:13:0 @@ -118,6 +118,7 @@ LIBUNBOUND_AGE=1 # 1.21.1 had 9:29:1 # 1.22.0 had 9:30:1 # 1.23.0 had 9:31:1 +# 1.23.1 had 9:32:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary diff --git a/doc/Changelog b/doc/Changelog index 56634766a..33113b192 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -5,6 +5,10 @@ Network and Information Security Lab, Tsinghua University for the report. +11 April 2025: Wouter + - Tag for 1.23.0rc2. This became the release of 1.23.0 on 24 April + 2025. The code repository continues with 1.23.1 in development. + 11 April 2025: Yorgos - Merge #1265: Fix WSAPoll. From c88fa02c1825509c7f04aec4950775cf1b9c9d3b Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 25 Apr 2025 11:12:28 +0200 Subject: [PATCH 111/218] - Fix #1272: assertion failure testcode/unitverify.c:202. --- doc/Changelog | 3 + testcode/testbound.c | 6 ++ testcode/unitverify.c | 129 +++++++++++++++++++++++++++++++++++++++ testdata/local_cname.rpl | 1 + 4 files changed, 139 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 33113b192..4d5f15ff4 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +25 April 2025: Wouter + - Fix #1272: assertion failure testcode/unitverify.c:202. + 16 April 2025: Wouter - Increase default to `num-queries-per-thread: 2048`, when unbound is compiled with libevent. It makes saturation of the task queue more diff --git a/testcode/testbound.c b/testcode/testbound.c index 6da4ceaf2..67af33b61 100644 --- a/testcode/testbound.c +++ b/testcode/testbound.c @@ -293,6 +293,12 @@ setup_config(FILE* in, int* lineno, int* pass_argc, char* pass_argv[]) fclose(cfg); return; } + if(strncmp(parse, "fake-sha1: yes", 14) == 0) { + /* Allow the use of SHA1 signatures for the test, + * in case that OpenSSL disallows use of RSASHA1 + * with rh-allow-sha1-signatures disabled. */ + setenv("OPENSSL_ENABLE_SHA1_SIGNATURES", "1", 0); + } fputs(line, cfg); } fatal_exit("No CONFIG_END in input file"); diff --git a/testcode/unitverify.c b/testcode/unitverify.c index 81c8b13c6..24274c58b 100644 --- a/testcode/unitverify.c +++ b/testcode/unitverify.c @@ -61,6 +61,12 @@ #include "sldns/str2wire.h" #include "sldns/wire2str.h" +#ifdef HAVE_SSL +#ifdef HAVE_OPENSSL_ERR_H +#include +#endif +#endif + /** verbose signature test */ static int vsig = 0; @@ -509,10 +515,133 @@ nsec3_hash_test(const char* fname) #define SRCDIRSTR xstr(SRCDIR) +#if defined(HAVE_SSL) && defined(USE_SHA1) +/* Detect if openssl is configured to disable RSASHA1 signatures, + * with the rh-allow-sha1-signatures disabled. */ +static int +rh_allow_sha1_signatures_disabled(void) +{ + EVP_MD_CTX* ctx; + EVP_PKEY* evp_key; + /* This key is rdata from nlnetlabs.nl DNSKEY from 20250424005001, + * with id=50602 (ksk), size=2048b. + * A 2048 bit key is taken to avoid key too small errors. */ + unsigned char key[] = { + 0x03, 0x01, 0x00, 0x01, 0xBC, 0x0B, 0xE8, 0xBB, + 0x97, 0x4C, 0xB5, 0xED, 0x6F, 0x6D, 0xC2, 0xB1, + 0x78, 0x69, 0x93, 0x1C, 0x72, 0x19, 0xB1, 0x05, + 0x51, 0x13, 0xA1, 0xFC, 0xBF, 0x01, 0x58, 0x0D, + 0x44, 0x10, 0x5F, 0x0B, 0x75, 0x0E, 0x11, 0x9A, + 0xC8, 0xF8, 0x0F, 0x90, 0xFC, 0xB8, 0x09, 0xD1, + 0x14, 0x39, 0x0D, 0x84, 0xCE, 0x97, 0x88, 0x82, + 0x3D, 0xC5, 0xCB, 0x1A, 0xBF, 0x00, 0x46, 0x37, + 0x01, 0xF1, 0xCD, 0x46, 0xA2, 0x8F, 0x83, 0x19, + 0x42, 0xED, 0x6F, 0xAF, 0x37, 0x1F, 0x18, 0x82, + 0x4B, 0x70, 0x2D, 0x50, 0xA5, 0xA6, 0x66, 0x48, + 0x7F, 0x56, 0xA8, 0x86, 0x05, 0x41, 0xC8, 0xBE, + 0x4F, 0x8B, 0x38, 0x51, 0xF0, 0xEB, 0xAD, 0x2F, + 0x7A, 0xC0, 0xEF, 0xC7, 0xD2, 0x72, 0x6F, 0x16, + 0x66, 0xAF, 0x59, 0x55, 0xFF, 0xEE, 0x9D, 0x50, + 0xE9, 0xDB, 0xF4, 0x02, 0xBC, 0x33, 0x5C, 0xC5, + 0xDA, 0x1C, 0x6A, 0xD1, 0x55, 0xD1, 0x20, 0x2B, + 0x63, 0x03, 0x4B, 0x77, 0x45, 0x46, 0x78, 0x31, + 0xE4, 0x90, 0xB9, 0x7F, 0x00, 0xFB, 0x62, 0x7C, + 0x07, 0xD3, 0xC1, 0x00, 0xA0, 0x54, 0x63, 0x74, + 0x0A, 0x17, 0x7B, 0xE7, 0xAD, 0x38, 0x07, 0x86, + 0x68, 0xE4, 0xFD, 0x20, 0x68, 0xD5, 0x33, 0x92, + 0xCA, 0x90, 0xDD, 0xA4, 0xE9, 0xF2, 0x11, 0xBD, + 0x9D, 0xA5, 0xF5, 0xEB, 0xB9, 0xFE, 0x8F, 0xA1, + 0xE4, 0xBF, 0xA4, 0xA4, 0x34, 0x5C, 0x6A, 0x95, + 0xB6, 0x42, 0x22, 0xF6, 0xD6, 0x10, 0x9C, 0x9B, + 0x0A, 0x56, 0xE7, 0x42, 0xE5, 0x7F, 0x1F, 0x4E, + 0xBE, 0x4F, 0x8C, 0xED, 0x30, 0x63, 0xA7, 0x88, + 0x93, 0xED, 0x37, 0x3C, 0x80, 0xBC, 0xD1, 0x66, + 0xBD, 0xB8, 0x2E, 0x65, 0xC4, 0xC8, 0x00, 0x5B, + 0xE7, 0x85, 0x96, 0xDD, 0xAA, 0x05, 0xE6, 0x4F, + 0x03, 0x64, 0xFA, 0x2D, 0xF6, 0x88, 0x14, 0x8F, + 0x15, 0x4D, 0xFD, 0xD3 + }; + size_t keylen = 260; + +#ifdef HAVE_EVP_MD_CTX_NEW + ctx = EVP_MD_CTX_new(); +#else + ctx = (EVP_MD_CTX*)malloc(sizeof(*ctx)); + if(ctx) EVP_MD_CTX_init(ctx); +#endif + if(!ctx) return 0; + + evp_key = sldns_key_rsa2pkey_raw(key, keylen); + if(!evp_key) { +#ifdef HAVE_EVP_MD_CTX_NEW + EVP_MD_CTX_destroy(ctx); +#else + EVP_MD_CTX_cleanup(ctx); + free(ctx); +#endif + return 0; + } + +#ifndef HAVE_EVP_DIGESTVERIFY + (void)evp_key; /* not used */ + if(EVP_DigestInit(ctx, EVP_sha1()) == 0) +#else + if(EVP_DigestVerifyInit(ctx, NULL, EVP_sha1(), NULL, evp_key) == 0) +#endif + { + unsigned long e = ERR_get_error(); +#ifdef EVP_R_INVALID_DIGEST + if (ERR_GET_LIB(e) == ERR_LIB_EVP && + ERR_GET_REASON(e) == EVP_R_INVALID_DIGEST) { + /* rh-allow-sha1-signatures makes use of sha1 invalid. */ + if(vsig) + printf("Detected that rh-allow-sha1-signatures is off, and disables SHA1 signatures\n"); +#ifdef HAVE_EVP_MD_CTX_NEW + EVP_MD_CTX_destroy(ctx); +#else + EVP_MD_CTX_cleanup(ctx); + free(ctx); +#endif + EVP_PKEY_free(evp_key); + return 1; + } +#endif /* EVP_R_INVALID_DIGEST */ + /* The signature verify failed for another reason. */ + log_crypto_err_code("EVP_DigestVerifyInit", e); +#ifdef HAVE_EVP_MD_CTX_NEW + EVP_MD_CTX_destroy(ctx); +#else + EVP_MD_CTX_cleanup(ctx); + free(ctx); +#endif + EVP_PKEY_free(evp_key); + return 0; + } +#ifdef HAVE_EVP_MD_CTX_NEW + EVP_MD_CTX_destroy(ctx); +#else + EVP_MD_CTX_cleanup(ctx); + free(ctx); +#endif + EVP_PKEY_free(evp_key); + return 0; +} +#endif /* HAVE_SSL && USE_SHA1 */ + void verify_test(void) { unit_show_feature("signature verify"); + +#if defined(HAVE_SSL) && defined(USE_SHA1) + if(rh_allow_sha1_signatures_disabled()) { + /* Allow the use of SHA1 signatures for the test, + * in case that OpenSSL disallows use of RSASHA1 + * with rh-allow-sha1-signatures disabled. */ + setenv("OPENSSL_ENABLE_SHA1_SIGNATURES", "1", 0); + } +#endif + #ifdef USE_SHA1 verifytest_file(SRCDIRSTR "/testdata/test_signatures.1", "20070818005004"); #endif diff --git a/testdata/local_cname.rpl b/testdata/local_cname.rpl index d68a2c40e..69a8b098b 100644 --- a/testdata/local_cname.rpl +++ b/testdata/local_cname.rpl @@ -11,6 +11,7 @@ server: # Use a fixed and faked date for DNSSEC validation to avoid run-time # re-signing test signatures. val-override-date: "20161001003725" + fake-sha1: yes define-tag: "cname cname2 nx servfail sec ambiguous" access-control-tag: 127.0.0.1/32 "cname cname2 nx servfail sec" From e5bbc36ae0d22c24c5ce40cf954b1385d64e64c3 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 28 Apr 2025 15:48:45 +0200 Subject: [PATCH 112/218] Use macros for the fr_check_changed* functions (#1275) --- daemon/remote.c | 305 ++++++++++++++++++------------------------------ 1 file changed, 111 insertions(+), 194 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 89134efc9..85cd77704 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -4348,37 +4348,45 @@ fr_check_tag_defines(struct fast_reload_thread* fr, struct config_file* newcfg) return 1; } -/** fast reload thread, check if config item has changed, if not add to - * the explanatory string. */ +/** fast reload thread, add incompatible option to the explanatory string */ static void -fr_check_changed_cfg(int cmp, const char* desc, char* str, size_t len) +fr_add_incompatible_option(const char* desc, char* str, size_t len) { - if(cmp) { - size_t slen = strlen(str); - size_t desclen = strlen(desc); - if(slen == 0) { - snprintf(str, len, "%s", desc); - return; - } - if(len - slen < desclen+2) - return; /* It does not fit */ - snprintf(str+slen, len-slen, " %s", desc); + size_t slen = strlen(str); + size_t desclen = strlen(desc); + if(slen == 0) { + snprintf(str, len, "%s", desc); + return; } + if(len - slen < desclen+2) + return; /* It does not fit */ + snprintf(str+slen, len-slen, " %s", desc); } +/** fast reload thread, check if config item has changed; thus incompatible */ +#define FR_CHECK_CHANGED_CFG(desc, var, str) \ +do { \ + if(cfg->var != newcfg->var) { \ + fr_add_incompatible_option(desc, str, sizeof(str)); \ + } \ +} while(0); + /** fast reload thread, check if config string has changed, checks NULLs. */ -static void -fr_check_changed_cfg_str(char* cmp1, char* cmp2, const char* desc, char* str, - size_t len) -{ - if((!cmp1 && cmp2) || - (cmp1 && !cmp2) || - (cmp1 && cmp2 && strcmp(cmp1, cmp2) != 0)) { - fr_check_changed_cfg(1, desc, str, len); - } -} +#define FR_CHECK_CHANGED_CFG_STR(desc, var, str) \ +do { \ + if((!cfg->var && newcfg->var) || \ + (cfg->var && !newcfg->var) || \ + (cfg->var && newcfg->var \ + && strcmp(cfg->var, newcfg->var) != 0)) { \ + fr_add_incompatible_option(desc, str, sizeof(str)); \ + } \ +} while(0); /** fast reload thread, check if config strlist has changed. */ +#define FR_CHECK_CHANGED_CFG_STRLIST(desc, var, str) do { \ + fr_check_changed_cfg_strlist(cfg->var, newcfg->var, desc, str, \ + sizeof(str)); \ + } while(0); static void fr_check_changed_cfg_strlist(struct config_strlist* cmp1, struct config_strlist* cmp2, const char* desc, char* str, size_t len) @@ -4389,18 +4397,22 @@ fr_check_changed_cfg_strlist(struct config_strlist* cmp1, (p1->str && !p2->str) || (p1->str && p2->str && strcmp(p1->str, p2->str) != 0)) { /* The strlist is different. */ - fr_check_changed_cfg(1, desc, str, len); + fr_add_incompatible_option(desc, str, len); return; } p1 = p1->next; p2 = p2->next; } if((!p1 && p2) || (p1 && !p2)) { - fr_check_changed_cfg(1, desc, str, len); + fr_add_incompatible_option(desc, str, len); } } /** fast reload thread, check if config str2list has changed. */ +#define FR_CHECK_CHANGED_CFG_STR2LIST(desc, var, buff) do { \ + fr_check_changed_cfg_str2list(cfg->var, newcfg->var, desc, buff,\ + sizeof(buff)); \ + } while(0); static void fr_check_changed_cfg_str2list(struct config_str2list* cmp1, struct config_str2list* cmp2, const char* desc, char* str, size_t len) @@ -4411,7 +4423,7 @@ fr_check_changed_cfg_str2list(struct config_str2list* cmp1, (p1->str && !p2->str) || (p1->str && p2->str && strcmp(p1->str, p2->str) != 0)) { /* The str2list is different. */ - fr_check_changed_cfg(1, desc, str, len); + fr_add_incompatible_option(desc, str, len); return; } if((!p1->str2 && p2->str2) || @@ -4419,14 +4431,14 @@ fr_check_changed_cfg_str2list(struct config_str2list* cmp1, (p1->str2 && p2->str2 && strcmp(p1->str2, p2->str2) != 0)) { /* The str2list is different. */ - fr_check_changed_cfg(1, desc, str, len); + fr_add_incompatible_option(desc, str, len); return; } p1 = p1->next; p2 = p2->next; } if((!p1 && p2) || (p1 && !p2)) { - fr_check_changed_cfg(1, desc, str, len); + fr_add_incompatible_option(desc, str, len); } } @@ -4440,98 +4452,54 @@ fr_check_compat_cfg(struct fast_reload_thread* fr, struct config_file* newcfg) changed_str[0]=0; /* Find incompatible options, and if so, print an error. */ - fr_check_changed_cfg(cfg->num_threads != newcfg->num_threads, - "num-threads", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->do_ip4 != newcfg->do_ip4, - "do-ip4", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->do_ip6 != newcfg->do_ip6, - "do-ip6", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->do_udp != newcfg->do_udp, - "do-udp", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->do_tcp != newcfg->do_tcp, - "do-tcp", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->port != newcfg->port, - "port", changed_str, sizeof(changed_str)); + FR_CHECK_CHANGED_CFG("num-threads", num_threads, changed_str); + FR_CHECK_CHANGED_CFG("do-ip4", do_ip4, changed_str); + FR_CHECK_CHANGED_CFG("do-ip6", do_ip6, changed_str); + FR_CHECK_CHANGED_CFG("do-udp", do_udp, changed_str); + FR_CHECK_CHANGED_CFG("do-tcp", do_tcp, changed_str); + FR_CHECK_CHANGED_CFG("port", port, changed_str); /* But cfg->outgoing_num_ports has been changed at startup, * possibly to reduce it, so do not check it here. */ - fr_check_changed_cfg(cfg->outgoing_num_tcp != newcfg->outgoing_num_tcp, - "outgoing-num-tcp", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->incoming_num_tcp != newcfg->incoming_num_tcp, - "incoming-num-tcp", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->num_out_ifs != newcfg->num_out_ifs, - "outgoing-interface", changed_str, sizeof(changed_str)); + FR_CHECK_CHANGED_CFG("outgoing-num-tcp", outgoing_num_tcp, changed_str); + FR_CHECK_CHANGED_CFG("incoming-num-tcp", incoming_num_tcp, changed_str); + FR_CHECK_CHANGED_CFG("outgoing-interface", num_out_ifs, changed_str); if(cfg->num_out_ifs == newcfg->num_out_ifs) { for(i=0; inum_out_ifs; i++) - fr_check_changed_cfg(strcmp(cfg->out_ifs[i], - newcfg->out_ifs[i]) != 0, "outgoing-interface", - changed_str, sizeof(changed_str)); + FR_CHECK_CHANGED_CFG_STR("outgoing-interface", + out_ifs[i], changed_str); } - fr_check_changed_cfg(cfg->num_ifs != newcfg->num_ifs, - "interface", changed_str, sizeof(changed_str)); + FR_CHECK_CHANGED_CFG("interface", num_ifs, changed_str); if(cfg->num_ifs == newcfg->num_ifs) { for(i=0; inum_ifs; i++) - fr_check_changed_cfg(strcmp(cfg->ifs[i], - newcfg->ifs[i]) != 0, "interface", - changed_str, sizeof(changed_str)); + FR_CHECK_CHANGED_CFG_STR("interface", + ifs[i], changed_str); } - fr_check_changed_cfg(cfg->if_automatic != newcfg->if_automatic, - "interface-automatic", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->so_rcvbuf != newcfg->so_rcvbuf, - "so-rcvbuf", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->so_sndbuf != newcfg->so_sndbuf, - "so-sndbuf", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->so_reuseport != newcfg->so_reuseport, - "so-reuseport", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->ip_transparent != newcfg->ip_transparent, - "ip-transparent", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->ip_freebind != newcfg->ip_freebind, - "ip-freebind", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->udp_connect != newcfg->udp_connect, - "udp-connect", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->msg_buffer_size != newcfg->msg_buffer_size, - "msg-buffer-size", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->do_tcp_keepalive != newcfg->do_tcp_keepalive, - "edns-tcp-keepalive", changed_str, sizeof(changed_str)); - fr_check_changed_cfg( - cfg->tcp_keepalive_timeout != newcfg->tcp_keepalive_timeout, - "edns-tcp-keepalive-timeout", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->tcp_idle_timeout != newcfg->tcp_idle_timeout, - "tcp-idle-timeout", changed_str, sizeof(changed_str)); + FR_CHECK_CHANGED_CFG("interface-automatic", if_automatic, changed_str); + FR_CHECK_CHANGED_CFG("so-rcvbuf", so_rcvbuf, changed_str); + FR_CHECK_CHANGED_CFG("so-sndbuf", so_sndbuf, changed_str); + FR_CHECK_CHANGED_CFG("so-reuseport", so_reuseport, changed_str); + FR_CHECK_CHANGED_CFG("ip-transparent", ip_transparent, changed_str); + FR_CHECK_CHANGED_CFG("ip-freebind", ip_freebind, changed_str); + FR_CHECK_CHANGED_CFG("udp-connect", udp_connect, changed_str); + FR_CHECK_CHANGED_CFG("msg-buffer-size", msg_buffer_size, changed_str); + FR_CHECK_CHANGED_CFG("edns-tcp-keepalive", do_tcp_keepalive, changed_str); + FR_CHECK_CHANGED_CFG("edns-tcp-keepalive-timeout", tcp_keepalive_timeout, changed_str); + FR_CHECK_CHANGED_CFG("tcp-idle-timeout", tcp_idle_timeout, changed_str); /* Not changed, only if DoH is used, it is then stored in commpoints, * as well as used from cfg. */ - fr_check_changed_cfg( - cfg->harden_large_queries != newcfg->harden_large_queries, - "harden-large-queries", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->http_max_streams != newcfg->http_max_streams, - "http-max-streams", changed_str, sizeof(changed_str)); - fr_check_changed_cfg_str(cfg->http_endpoint, newcfg->http_endpoint, - "http-endpoint", changed_str, sizeof(changed_str)); - fr_check_changed_cfg( - cfg->http_notls_downstream != newcfg->http_notls_downstream, - "http_notls_downstream", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->https_port != newcfg->https_port, - "https-port", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->ssl_port != newcfg->ssl_port, - "tls-port", changed_str, sizeof(changed_str)); - fr_check_changed_cfg_str(cfg->ssl_service_key, newcfg->ssl_service_key, - "tls-service-key", changed_str, sizeof(changed_str)); - fr_check_changed_cfg_str(cfg->ssl_service_pem, newcfg->ssl_service_pem, - "tls-service-pem", changed_str, sizeof(changed_str)); - fr_check_changed_cfg_str(cfg->tls_cert_bundle, newcfg->tls_cert_bundle, - "tls-cert-bundle", changed_str, sizeof(changed_str)); - fr_check_changed_cfg_strlist(cfg->proxy_protocol_port, - newcfg->proxy_protocol_port, "proxy-protocol-port", - changed_str, sizeof(changed_str)); - fr_check_changed_cfg_strlist(cfg->tls_additional_port, - newcfg->tls_additional_port, "tls-additional-port", - changed_str, sizeof(changed_str)); - fr_check_changed_cfg_str(cfg->if_automatic_ports, - newcfg->if_automatic_ports, "interface-automatic-ports", - changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->udp_upstream_without_downstream != - newcfg->udp_upstream_without_downstream, - "udp-upstream-without-downstream", changed_str, - sizeof(changed_str)); + FR_CHECK_CHANGED_CFG("harden-large-queries", harden_large_queries, changed_str); + FR_CHECK_CHANGED_CFG("http-max-streams", http_max_streams, changed_str); + FR_CHECK_CHANGED_CFG_STR("http-endpoint", http_endpoint, changed_str); + FR_CHECK_CHANGED_CFG("http_notls_downstream", http_notls_downstream, changed_str); + FR_CHECK_CHANGED_CFG("https-port", https_port, changed_str); + FR_CHECK_CHANGED_CFG("tls-port", ssl_port, changed_str); + FR_CHECK_CHANGED_CFG_STR("tls-service-key", ssl_service_key, changed_str); + FR_CHECK_CHANGED_CFG_STR("tls-service-pem", ssl_service_pem, changed_str); + FR_CHECK_CHANGED_CFG_STR("tls-cert-bundle", tls_cert_bundle, changed_str); + FR_CHECK_CHANGED_CFG_STRLIST("proxy-protocol-port", proxy_protocol_port, changed_str); + FR_CHECK_CHANGED_CFG_STRLIST("tls-additional-port", tls_additional_port, changed_str); + FR_CHECK_CHANGED_CFG_STR("interface-automatic-ports", if_automatic_ports, changed_str); + FR_CHECK_CHANGED_CFG("udp-upstream-without-downstream", udp_upstream_without_downstream, changed_str); if(changed_str[0] != 0) { /* The new config changes some items that do not work with @@ -4549,7 +4517,7 @@ fr_check_compat_cfg(struct fast_reload_thread* fr, struct config_file* newcfg) /** fast reload thread, check nopause config items */ static int -fr_check_nopause_cfg(struct fast_reload_thread* fr, struct config_file* newcfg) +fr_check_nopause_compat_cfg(struct fast_reload_thread* fr, struct config_file* newcfg) { char changed_str[1024]; struct config_file* cfg = fr->worker->env.cfg; @@ -4558,94 +4526,43 @@ fr_check_nopause_cfg(struct fast_reload_thread* fr, struct config_file* newcfg) changed_str[0]=0; /* Check for iter_env. */ - fr_check_changed_cfg( - cfg->outbound_msg_retry != newcfg->outbound_msg_retry, - "outbound-msg-retry", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->max_sent_count != newcfg->max_sent_count, - "max-sent-count", changed_str, sizeof(changed_str)); - fr_check_changed_cfg( - cfg->max_query_restarts != newcfg->max_query_restarts, - "max-query-restarts", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(strcmp(cfg->target_fetch_policy, - newcfg->target_fetch_policy) != 0, - "target-fetch-policy", changed_str, sizeof(changed_str)); - fr_check_changed_cfg( - cfg->donotquery_localhost != newcfg->donotquery_localhost, - "do-not-query-localhost", changed_str, sizeof(changed_str)); - fr_check_changed_cfg_strlist(cfg->donotqueryaddrs, - newcfg->donotqueryaddrs, "do-not-query-localhost", - changed_str, sizeof(changed_str)); - fr_check_changed_cfg_strlist(cfg->private_address, - newcfg->private_address, "private-address", - changed_str, sizeof(changed_str)); - fr_check_changed_cfg_strlist(cfg->private_domain, - newcfg->private_domain, "private-domain", - changed_str, sizeof(changed_str)); - fr_check_changed_cfg_strlist(cfg->caps_whitelist, - newcfg->caps_whitelist, "caps-exempt", - changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->do_nat64 != newcfg->do_nat64, - "do-nat64", changed_str, sizeof(changed_str)); - fr_check_changed_cfg_str(cfg->nat64_prefix, newcfg->nat64_prefix, - "nat64-prefix", changed_str, sizeof(changed_str)); + FR_CHECK_CHANGED_CFG("outbound-msg-retry", outbound_msg_retry, changed_str); + FR_CHECK_CHANGED_CFG("max-sent-count", max_sent_count, changed_str); + FR_CHECK_CHANGED_CFG("max-query-restarts", max_query_restarts, changed_str); + FR_CHECK_CHANGED_CFG_STR("target-fetch-policy", target_fetch_policy, changed_str); + FR_CHECK_CHANGED_CFG("do-not-query-localhost", donotquery_localhost, changed_str); + FR_CHECK_CHANGED_CFG_STRLIST("do-not-query-address", donotqueryaddrs, changed_str); + FR_CHECK_CHANGED_CFG_STRLIST("private-address", private_address, changed_str); + FR_CHECK_CHANGED_CFG_STRLIST("private-domain", private_domain, changed_str); + FR_CHECK_CHANGED_CFG_STRLIST("caps-exempt", caps_whitelist, changed_str); + FR_CHECK_CHANGED_CFG("do-nat64", do_nat64, changed_str); + FR_CHECK_CHANGED_CFG_STR("nat64-prefix", nat64_prefix, changed_str); /* Check for val_env. */ - fr_check_changed_cfg(cfg->bogus_ttl != newcfg->bogus_ttl, - "val-bogus-ttl", changed_str, sizeof(changed_str)); - fr_check_changed_cfg( - cfg->val_date_override != newcfg->val_date_override, - "val-date-override", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->val_sig_skew_min != newcfg->val_sig_skew_min, - "val-sig-skew-min", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->val_sig_skew_max != newcfg->val_sig_skew_max, - "val-sig-skew-max", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(cfg->val_max_restart != newcfg->val_max_restart, - "val-max-restart", changed_str, sizeof(changed_str)); - fr_check_changed_cfg(strcmp(cfg->val_nsec3_key_iterations, - newcfg->val_nsec3_key_iterations) != 0, - "val-nsec3-keysize-iterations", changed_str, - sizeof(changed_str)); + FR_CHECK_CHANGED_CFG("val-bogus-ttl", bogus_ttl, changed_str); + FR_CHECK_CHANGED_CFG("val-date-override", val_date_override, changed_str); + FR_CHECK_CHANGED_CFG("val-sig-skew-min", val_sig_skew_min, changed_str); + FR_CHECK_CHANGED_CFG("val-sig-skew-max", val_sig_skew_max, changed_str); + FR_CHECK_CHANGED_CFG("val-max-restart", val_max_restart, changed_str); + FR_CHECK_CHANGED_CFG_STR("val-nsec3-keysize-iterations", + val_nsec3_key_iterations, changed_str); /* Check for infra. */ - fr_check_changed_cfg(cfg->host_ttl != newcfg->host_ttl, - "infra-host-ttl", changed_str, sizeof(changed_str)); - fr_check_changed_cfg( - cfg->infra_keep_probing != newcfg->infra_keep_probing, - "infra-keep-probing", changed_str, sizeof(changed_str)); - fr_check_changed_cfg( - cfg->ratelimit != newcfg->ratelimit, - "ratelimit", changed_str, sizeof(changed_str)); - fr_check_changed_cfg( - cfg->ip_ratelimit != newcfg->ip_ratelimit, - "ip-ratelimit", changed_str, sizeof(changed_str)); - fr_check_changed_cfg( - cfg->ip_ratelimit_cookie != newcfg->ip_ratelimit_cookie, - "ip-ratelimit-cookie", changed_str, sizeof(changed_str)); - fr_check_changed_cfg_str2list(cfg->wait_limit_netblock, - newcfg->wait_limit_netblock, "wait-limit-netblock", - changed_str, sizeof(changed_str)); - fr_check_changed_cfg_str2list(cfg->wait_limit_cookie_netblock, - newcfg->wait_limit_cookie_netblock, - "wait-limit-cookie-netblock", changed_str, - sizeof(changed_str)); - fr_check_changed_cfg_str2list(cfg->ratelimit_below_domain, - newcfg->ratelimit_below_domain, "ratelimit-below-domain", - changed_str, sizeof(changed_str)); - fr_check_changed_cfg_str2list(cfg->ratelimit_for_domain, - newcfg->ratelimit_for_domain, "ratelimit-for-domain", - changed_str, sizeof(changed_str)); + FR_CHECK_CHANGED_CFG("infra-host-ttl", host_ttl, changed_str); + FR_CHECK_CHANGED_CFG("infra-keep-probing", infra_keep_probing, changed_str); + FR_CHECK_CHANGED_CFG("ratelimit", ratelimit, changed_str); + FR_CHECK_CHANGED_CFG("ip-ratelimit", ip_ratelimit, changed_str); + FR_CHECK_CHANGED_CFG("ip-ratelimit-cookie", ip_ratelimit_cookie, changed_str); + FR_CHECK_CHANGED_CFG_STR2LIST("wait-limit-netblock", wait_limit_netblock, changed_str); + FR_CHECK_CHANGED_CFG_STR2LIST("wait-limit-cookie-netblock", wait_limit_cookie_netblock, changed_str); + FR_CHECK_CHANGED_CFG_STR2LIST("ratelimit-below-domain", ratelimit_below_domain, changed_str); + FR_CHECK_CHANGED_CFG_STR2LIST("ratelimit-for-domain", ratelimit_for_domain, changed_str); /* Check for dnstap. */ - fr_check_changed_cfg( - cfg->dnstap_send_identity != newcfg->dnstap_send_identity, - "dnstap-send-identity", changed_str, sizeof(changed_str)); - fr_check_changed_cfg( - cfg->dnstap_send_version != newcfg->dnstap_send_version, - "dnstap-send-version", changed_str, sizeof(changed_str)); - fr_check_changed_cfg_str(cfg->dnstap_identity, newcfg->dnstap_identity, - "dnstap-identity", changed_str, sizeof(changed_str)); - fr_check_changed_cfg_str(cfg->dnstap_version, newcfg->dnstap_version, - "dnstap-version", changed_str, sizeof(changed_str)); + FR_CHECK_CHANGED_CFG("dnstap-send-identity", dnstap_send_identity, changed_str); + FR_CHECK_CHANGED_CFG("dnstap-send-version", dnstap_send_version, changed_str); + FR_CHECK_CHANGED_CFG_STR("dnstap-identity", dnstap_identity, changed_str); + FR_CHECK_CHANGED_CFG_STR("dnstap-version", dnstap_version, changed_str); if(changed_str[0] != 0) { /* The new config changes some items that need a pause, @@ -6193,7 +6110,7 @@ fr_load_config(struct fast_reload_thread* fr, struct timeval* time_read, config_delete(newcfg); return 0; } - if(!fr_check_nopause_cfg(fr, newcfg)) { + if(!fr_check_nopause_compat_cfg(fr, newcfg)) { config_delete(newcfg); return 0; } From 0f95fae445e7871edd07e0167796cf070739953b Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 28 Apr 2025 15:50:03 +0200 Subject: [PATCH 113/218] Changelog entry for #1275: - Merge #1275: Use macros for the fr_check_changed* functions. --- doc/Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 4d5f15ff4..f92f2f645 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +28 April 2025: Yorgos + - Merge #1275: Use macros for the fr_check_changed* functions. + 25 April 2025: Wouter - Fix #1272: assertion failure testcode/unitverify.c:202. From c253c8367a627151aab5ebbe0bbe6722f0630af6 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 29 Apr 2025 12:38:41 +0200 Subject: [PATCH 114/218] - Fix for parallel build of dnstap protoc-c output. --- Makefile.in | 6 +++++- doc/Changelog | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index 463cdac28..76ad6bcb9 100644 --- a/Makefile.in +++ b/Makefile.in @@ -449,9 +449,13 @@ dnstap.lo dnstap.o: $(srcdir)/dnstap/dnstap.c config.h dnstap/dnstap_config.h \ $(srcdir)/util/netevent.h $(srcdir)/util/net_help.h \ $(srcdir)/util/locks.h -dnstap/dnstap.pb-c.c dnstap/dnstap.pb-c.h: $(srcdir)/dnstap/dnstap.proto +# Builds both dnstap/dnstap.pb-c.c and dnstap/dnstap.pb-c.h. +# To avoid double-building we split one target out. +dnstap/dnstap.pb-c.c: $(srcdir)/dnstap/dnstap.proto @-if test ! -d dnstap; then $(INSTALL) -d dnstap; fi $(PROTOC_C) --c_out=. --proto_path=$(srcdir) $(srcdir)/dnstap/dnstap.proto +dnstap/dnstap.pb-c.h: dnstap/dnstap.pb-c.c + touch $@ unbound-dnstap-socket$(EXEEXT): $(DNSTAP_SOCKET_OBJ_LINK) $(LINK) -o $@ $(DNSTAP_SOCKET_OBJ_LINK) $(SSLLIB) $(LIBS) diff --git a/doc/Changelog b/doc/Changelog index f92f2f645..68a10be60 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +29 April 2025: Wouter + - Fix for parallel build of dnstap protoc-c output. + 28 April 2025: Yorgos - Merge #1275: Use macros for the fr_check_changed* functions. From a904a3a2c29a5440d1e1bb0802b064c40fd2626a Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 29 Apr 2025 12:43:56 +0200 Subject: [PATCH 115/218] - Fix dnstap to use protoc. --- configure | 60 ++++++++++++++++++++++++++++++++++++++++++++---- dnstap/dnstap.m4 | 15 ++++++++---- doc/Changelog | 1 + 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/configure b/configure index ac1a22790..adbd18b9e 100755 --- a/configure +++ b/configure @@ -686,6 +686,7 @@ ENABLE_DNSTAP PROTOBUFC_LIBS PROTOBUFC_CFLAGS PROTOC_C +PROTOC UBSYMS EXTRALINK COMMON_OBJ_ALL_SYMBOLS @@ -24249,7 +24250,55 @@ fi if test "x$opt_dnstap" != "xno"; then - # Extract the first word of "protoc-c", so it can be a program name with args. + # Extract the first word of "protoc", so it can be a program name with args. +set dummy protoc; ac_word=$2 +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_path_PROTOC+y} +then : + printf %s "(cached) " >&6 +else $as_nop + case $PROTOC in + [\\/]* | ?:[\\/]*) + ac_cv_path_PROTOC="$PROTOC" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_path_PROTOC="$as_dir$ac_word$ac_exec_ext" + printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +PROTOC=$ac_cv_path_PROTOC +if test -n "$PROTOC"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PROTOC" >&5 +printf "%s\n" "$PROTOC" >&6; } +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } +fi + + + # 'protoc-c' is deprecated. We use 'protoc' instead. If it can not be + # found, try 'protoc-c'. + if test -z "$PROTOC"; then + # Extract the first word of "protoc-c", so it can be a program name with args. set dummy protoc-c; ac_word=$2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 printf %s "checking for $ac_word... " >&6; } @@ -24294,9 +24343,12 @@ printf "%s\n" "no" >&6; } fi - if test -z "$PROTOC_C"; then - as_fn_error $? "The protoc-c program was not found. Please install protobuf-c!" "$LINENO" 5 - fi + else + PROTOC_C="$PROTOC" + fi + if test -z "$PROTOC_C"; then + as_fn_error $? "The protoc or protoc-c program was not found. It is needed for dnstap, use --disable-dnstap, or install protobuf-c to provide protoc or protoc-c" "$LINENO" 5 + fi # Check whether --with-protobuf-c was given. if test ${with_protobuf_c+y} diff --git a/dnstap/dnstap.m4 b/dnstap/dnstap.m4 index 78d0dd68b..166402d81 100644 --- a/dnstap/dnstap.m4 +++ b/dnstap/dnstap.m4 @@ -18,10 +18,17 @@ AC_DEFUN([dt_DNSTAP], [opt_dnstap_socket_path="$1"]) if test "x$opt_dnstap" != "xno"; then - AC_PATH_PROG([PROTOC_C], [protoc-c]) - if test -z "$PROTOC_C"; then - AC_MSG_ERROR([The protoc-c program was not found. Please install protobuf-c!]) - fi + AC_PATH_PROG([PROTOC], [protoc]) + # 'protoc-c' is deprecated. We use 'protoc' instead. If it can not be + # found, try 'protoc-c'. + if test -z "$PROTOC"; then + AC_PATH_PROG([PROTOC_C], [protoc-c]) + else + PROTOC_C="$PROTOC" + fi + if test -z "$PROTOC_C"; then + AC_MSG_ERROR([[The protoc or protoc-c program was not found. It is needed for dnstap, use --disable-dnstap, or install protobuf-c to provide protoc or protoc-c]]) + fi AC_ARG_WITH([protobuf-c], AS_HELP_STRING([--with-protobuf-c=path], [Path where protobuf-c is installed, for dnstap]), [ diff --git a/doc/Changelog b/doc/Changelog index 68a10be60..bdbfc3adc 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,5 +1,6 @@ 29 April 2025: Wouter - Fix for parallel build of dnstap protoc-c output. + - Fix dnstap to use protoc. 28 April 2025: Yorgos - Merge #1275: Use macros for the fr_check_changed* functions. From fcc21885e49f3f3bd0645aec2603abd714165f9a Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 29 Apr 2025 15:21:47 +0200 Subject: [PATCH 116/218] Auto-configure '-slabs' values (#1276) - Auto-configure '-slabs' values to a power of 2 value close to num-threads by default for multi-threaded environments. Co-authored-by: Wouter Wijngaards --- doc/unbound.conf.5.in | 68 ++++++++++++++++++++++++++++++------------- testcode/unitauth.c | 1 + testcode/unitinfra.c | 1 + testcode/unitzonemd.c | 1 + util/config_file.c | 58 +++++++++++++++++++++++++++++++----- util/config_file.h | 11 +++++++ 6 files changed, 111 insertions(+), 29 deletions(-) diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 4c7ea9f75..e963a3e3a 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -259,9 +259,12 @@ A plain number is in bytes, append 'k', 'm' or 'g' for kilobytes, megabytes or gigabytes (1024*1024 bytes in a megabyte). .TP .B msg\-cache\-slabs: \fI -Number of slabs in the message cache. Slabs reduce lock contention by threads. -Must be set to a power of 2. Setting (close) to the number of cpus is a -reasonable guess. +Number of slabs in the message cache. +Slabs reduce lock contention by threads. +Must be set to a power of 2. +Setting (close) to the number of cpus is a fairly good setting. +If left unconfigured, it will be configured automatically to be a power of 2 +close to the number of configured threads in multi-threaded environments. .TP .B num\-queries\-per\-thread: \fI The number of queries that every thread will service simultaneously. @@ -400,8 +403,12 @@ A plain number is in bytes, append 'k', 'm' or 'g' for kilobytes, megabytes or gigabytes (1024*1024 bytes in a megabyte). .TP .B rrset\-cache\-slabs: \fI -Number of slabs in the RRset cache. Slabs reduce lock contention by threads. +Number of slabs in the RRset cache. +Slabs reduce lock contention by threads. Must be set to a power of 2. +Setting (close) to the number of cpus is a fairly good setting. +If left unconfigured, it will be configured automatically to be a power of 2 +close to the number of configured threads in multi-threaded environments. .TP .B cache\-max\-ttl: \fI Time to live maximum for RRsets and messages in the cache. Default is @@ -436,8 +443,12 @@ Time to live for entries in the host cache. The host cache contains roundtrip timing, lameness and EDNS support information. Default is 900. .TP .B infra\-cache\-slabs: \fI -Number of slabs in the infrastructure cache. Slabs reduce lock contention -by threads. Must be set to a power of 2. +Number of slabs in the infrastructure cache. +Slabs reduce lock contention by threads. +Must be set to a power of 2. +Setting (close) to the number of cpus is a fairly good setting. +If left unconfigured, it will be configured automatically to be a power of 2 +close to the number of configured threads in multi-threaded environments. .TP .B infra\-cache\-numhosts: \fI Number of hosts for which information is cached. Default is 10000. @@ -1494,9 +1505,12 @@ A plain number is in bytes, append 'k', 'm' or 'g' for kilobytes, megabytes or gigabytes (1024*1024 bytes in a megabyte). .TP .B key\-cache\-slabs: \fI -Number of slabs in the key cache. Slabs reduce lock contention by threads. -Must be set to a power of 2. Setting (close) to the number of cpus is a -reasonable guess. +Number of slabs in the key cache. +Slabs reduce lock contention by threads. +Must be set to a power of 2. +Setting (close) to the number of cpus is a fairly good setting. +If left unconfigured, it will be configured automatically to be a power of 2 +close to the number of configured threads in multi-threaded environments. .TP .B neg\-cache\-size: \fI Number of bytes size of the aggressive negative cache. Default is 1 megabyte. @@ -1903,9 +1917,12 @@ The ratelimit structure is small, so this data structure likely does not need to be large. .TP 5 .B ratelimit\-slabs: \fI -Give power of 2 number of slabs, this is used to reduce lock contention -in the ratelimit tracking data structure. Close to the number of cpus is -a fairly good setting. +Number of slabs in the ratelimit tracking data structure. +Slabs reduce lock contention by threads. +Must be set to a power of 2. +Setting (close) to the number of cpus is a fairly good setting. +If left unconfigured, it will be configured automatically to be a power of 2 +close to the number of configured threads in multi-threaded environments. .TP 5 .B ratelimit\-factor: \fI Set the amount of queries to rate limit when the limit is exceeded. @@ -1974,9 +1991,12 @@ The ip ratelimit structure is small, so this data structure likely does not need to be large. .TP 5 .B ip\-ratelimit\-slabs: \fI -Give power of 2 number of slabs, this is used to reduce lock contention -in the ip ratelimit tracking data structure. Close to the number of cpus is -a fairly good setting. +Number of slabs in the ip ratelimit tracking data structure. +Slabs reduce lock contention by threads. +Must be set to a power of 2. +Setting (close) to the number of cpus is a fairly good setting. +If left unconfigured, it will be configured automatically to be a power of 2 +close to the number of configured threads in multi-threaded environments. .TP 5 .B ip\-ratelimit\-factor: \fI Set the amount of queries to rate limit when the limit is exceeded. @@ -2610,9 +2630,12 @@ The shared secret cache is used when a same client is making multiple queries using the same public key. It saves a substantial amount of CPU. .TP .B dnscrypt\-shared\-secret\-cache\-slabs: \fI -Give power of 2 number of slabs, this is used to reduce lock contention -in the dnscrypt shared secrets cache. Close to the number of cpus is -a fairly good setting. +Number of slabs in the dnscrypt shared secrets cache. +Slabs reduce lock contention by threads. +Must be set to a power of 2. +Setting (close) to the number of cpus is a fairly good setting. +If left unconfigured, it will be configured automatically to be a power of 2 +close to the number of configured threads in multi-threaded environments. .TP .B dnscrypt\-nonce\-cache\-size: \fI Give the size of the data structure in which the client nonces are kept in. @@ -2621,9 +2644,12 @@ The nonce cache is used to prevent dnscrypt message replaying. Client nonce should be unique for any pair of client pk/server sk. .TP .B dnscrypt\-nonce\-cache\-slabs: \fI -Give power of 2 number of slabs, this is used to reduce lock contention -in the dnscrypt nonce cache. Close to the number of cpus is -a fairly good setting. +Number of slabs in the dnscrypt nonce cache. +Slabs reduce lock contention by threads. +Must be set to a power of 2. +Setting (close) to the number of cpus is a fairly good setting. +If left unconfigured, it will be configured automatically to be a power of 2 +close to the number of configured threads in multi-threaded environments. .SS "EDNS Client Subnet Module Options" .LP The ECS module must be configured in the \fBmodule\-config:\fR directive e.g., diff --git a/testcode/unitauth.c b/testcode/unitauth.c index 23c57e095..401e93af3 100644 --- a/testcode/unitauth.c +++ b/testcode/unitauth.c @@ -670,6 +670,7 @@ authtest_addzone(struct auth_zones* az, const char* name, char* fname) auth_zone_set_zonefile(z, fname); z->for_upstream = 1; cfg = config_create(); + config_auto_slab_values(cfg); free(cfg->chrootdir); cfg->chrootdir = NULL; diff --git a/testcode/unitinfra.c b/testcode/unitinfra.c index 6834c51ee..91a88f6ae 100644 --- a/testcode/unitinfra.c +++ b/testcode/unitinfra.c @@ -131,6 +131,7 @@ void infra_test(void) unit_show_feature("infra cache"); unit_assert(ipstrtoaddr("127.0.0.1", 53, &one, &onelen)); + config_auto_slab_values(cfg); slab = infra_create(cfg); /* insert new record */ unit_assert( infra_host(slab, &one, onelen, zone, zonelen, now, diff --git a/testcode/unitzonemd.c b/testcode/unitzonemd.c index 63dc13eda..0420b0361 100644 --- a/testcode/unitzonemd.c +++ b/testcode/unitzonemd.c @@ -267,6 +267,7 @@ static void zonemd_verify_test(char* zname, char* zfile, char* tastr, env.cfg = config_create(); if(!env.cfg) fatal_exit("out of memory"); + config_auto_slab_values(env.cfg); env.now = &now; env.cfg->val_date_override = cfg_convert_timeval(date_override); if(!env.cfg->val_date_override) diff --git a/util/config_file.c b/util/config_file.c index 7566d690c..f1a26674e 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -169,10 +169,10 @@ config_create(void) cfg->edns_buffer_size = 1232; /* from DNS flagday recommendation */ cfg->msg_buffer_size = 65552; /* 64 k + a small margin */ cfg->msg_cache_size = 4 * 1024 * 1024; - cfg->msg_cache_slabs = 4; + cfg->msg_cache_slabs = 0; cfg->jostle_time = 200; cfg->rrset_cache_size = 4 * 1024 * 1024; - cfg->rrset_cache_slabs = 4; + cfg->rrset_cache_slabs = 0; cfg->host_ttl = 900; cfg->bogus_ttl = 60; cfg->min_ttl = 0; @@ -182,7 +182,7 @@ config_create(void) cfg->prefetch = 0; cfg->prefetch_key = 0; cfg->deny_any = 0; - cfg->infra_cache_slabs = 4; + cfg->infra_cache_slabs = 0; cfg->infra_cache_numhosts = 10000; cfg->infra_cache_min_rtt = 50; cfg->infra_cache_max_rtt = 120000; @@ -291,7 +291,7 @@ config_create(void) cfg->keep_missing = 366*24*3600; /* one year plus a little leeway */ cfg->permit_small_holddown = 0; cfg->key_cache_size = 4 * 1024 * 1024; - cfg->key_cache_slabs = 4; + cfg->key_cache_slabs = 0; cfg->neg_cache_size = 1 * 1024 * 1024; cfg->local_zones = NULL; cfg->local_zones_nodefault = NULL; @@ -341,8 +341,8 @@ config_create(void) cfg->ip_ratelimit_cookie = 0; cfg->ip_ratelimit = 0; cfg->ratelimit = 0; - cfg->ip_ratelimit_slabs = 4; - cfg->ratelimit_slabs = 4; + cfg->ip_ratelimit_slabs = 0; + cfg->ratelimit_slabs = 0; cfg->ip_ratelimit_size = 4*1024*1024; cfg->ratelimit_size = 4*1024*1024; cfg->ratelimit_for_domain = NULL; @@ -367,9 +367,9 @@ config_create(void) cfg->dnscrypt_provider_cert_rotated = NULL; cfg->dnscrypt_secret_key = NULL; cfg->dnscrypt_shared_secret_cache_size = 4*1024*1024; - cfg->dnscrypt_shared_secret_cache_slabs = 4; + cfg->dnscrypt_shared_secret_cache_slabs = 0; cfg->dnscrypt_nonce_cache_size = 4*1024*1024; - cfg->dnscrypt_nonce_cache_slabs = 4; + cfg->dnscrypt_nonce_cache_slabs = 0; cfg->pad_responses = 1; cfg->pad_responses_block_size = 468; /* from RFC8467 */ cfg->pad_queries = 1; @@ -454,6 +454,11 @@ struct config_file* config_create_forlib(void) cfg->val_log_squelch = 1; cfg->minimal_responses = 0; cfg->harden_short_bufsize = 1; + /* Need to explicitly define the slabs from their 0 default value */ + cfg->ip_ratelimit_slabs = 1; + cfg->ratelimit_slabs = 1; + cfg->dnscrypt_shared_secret_cache_slabs = 1; + cfg->dnscrypt_nonce_cache_slabs = 1; return cfg; } @@ -1448,6 +1453,41 @@ create_cfg_parser(struct config_file* cfg, char* filename, const char* chroot) init_cfg_parse(); } +void +config_auto_slab_values(struct config_file* cfg) +{ +#define SET_AUTO_SLAB(var, name, val) \ +do { \ + if(cfg->var == 0) { \ + cfg->var = val; \ + verbose(VERB_QUERY, "setting "name": %lu", (unsigned long)val); \ + } \ +} while(0); +#ifdef THREADS_DISABLED + size_t pow_2_threads = 1; +#else + size_t pow_2_threads = 4; /* pow2 start */ + while (pow_2_threads < (size_t)(cfg->num_threads?cfg->num_threads:1) && + /* 1/3 of the distance to the next pow2 value stays with the + * lower value */ + (size_t)cfg->num_threads > pow_2_threads + (pow_2_threads - 1)/3) { + pow_2_threads <<= 1; + } + log_assert((pow_2_threads & (pow_2_threads - 1)) == 0); /* powerof2? */ +#endif /* THREADS_DISABLED */ + + SET_AUTO_SLAB(msg_cache_slabs, "msg-cache-slabs", pow_2_threads); + SET_AUTO_SLAB(rrset_cache_slabs, "rrset-cache-slabs", pow_2_threads); + SET_AUTO_SLAB(infra_cache_slabs, "infra-cache-slabs", pow_2_threads); + SET_AUTO_SLAB(key_cache_slabs, "key-cache-slabs", pow_2_threads); + SET_AUTO_SLAB(ip_ratelimit_slabs, "ip-ratelimit-slabs", pow_2_threads); + SET_AUTO_SLAB(ratelimit_slabs, "ratelimit-slabs", pow_2_threads); + SET_AUTO_SLAB(dnscrypt_shared_secret_cache_slabs, + "dnscrypt-shared-secret-cache-slabs", pow_2_threads); + SET_AUTO_SLAB(dnscrypt_nonce_cache_slabs, + "dnscrypt-nonce-cache-slabs", pow_2_threads); +} + int config_read(struct config_file* cfg, const char* filename, const char* chroot) { @@ -1512,6 +1552,7 @@ config_read(struct config_file* cfg, const char* filename, const char* chroot) } } globfree(&g); + config_auto_slab_values(cfg); return 1; } #endif /* HAVE_GLOB */ @@ -1535,6 +1576,7 @@ config_read(struct config_file* cfg, const char* filename, const char* chroot) return 0; } + config_auto_slab_values(cfg); return 1; } diff --git a/util/config_file.h b/util/config_file.h index 89bbc1c7d..44ac036b8 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -966,6 +966,17 @@ struct config_file* config_create(void); */ struct config_file* config_create_forlib(void); +/** + * If _slabs values are not explicitly configured, 0 value, put them in a + * pow2 value close to the number of threads used. + * Starts at the current default 4. + * If num_threads is in between two pow2 values, 1/3 of the way stays with + * the lower pow2 value. + * Exported for unit testing. + * @param config: where the _slabs values reside. + */ +void config_auto_slab_values(struct config_file* config); + /** * Read the config file from the specified filename. * @param config: where options are stored into, must be freshly created. From b50faccb1df1ed27ff6d16dc2df2b349e326e2d7 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 29 Apr 2025 15:23:07 +0200 Subject: [PATCH 117/218] Changelog entry for #1276: - Merge #1276: Auto-configure '-slabs' values. --- doc/Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index bdbfc3adc..e96a9eba7 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,9 @@ - Fix for parallel build of dnstap protoc-c output. - Fix dnstap to use protoc. +29 April 2025: Yorgos + - Merge #1276: Auto-configure '-slabs' values. + 28 April 2025: Yorgos - Merge #1275: Use macros for the fr_check_changed* functions. From 5dd14e26443a3801eea1e04cd650822183fe4762 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 5 May 2025 14:47:12 +0200 Subject: [PATCH 118/218] - Sync unbound and unbound-checkconf log output for unknown modules. --- doc/Changelog | 3 +++ services/modstack.c | 4 ++-- smallapp/unbound-checkconf.c | 6 ++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index e96a9eba7..e97eed9bf 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +5 May 2025: Yorgos + - Sync unbound and unbound-checkconf log output for unknown modules. + 29 April 2025: Wouter - Fix for parallel build of dnstap protoc-c output. - Fix dnstap to use protoc. diff --git a/services/modstack.c b/services/modstack.c index fa68cc71d..2bc79c4ad 100644 --- a/services/modstack.c +++ b/services/modstack.c @@ -138,8 +138,8 @@ modstack_config(struct module_stack* stack, const char* module_conf) if(strchr(s, ' ')) *(strchr(s, ' ')) = 0; if(strchr(s, '\t')) *(strchr(s, '\t')) = 0; log_err("Unknown value in module-config, module: '%s'." - " This module is not present (not compiled in)," - " See the list of linked modules with unbound -V", s); + " This module is not present (not compiled in);" + " see the list of linked modules with unbound -V", s); return 0; } } diff --git a/smallapp/unbound-checkconf.c b/smallapp/unbound-checkconf.c index 3b7ba758a..9dd73fa8f 100644 --- a/smallapp/unbound-checkconf.c +++ b/smallapp/unbound-checkconf.c @@ -636,8 +636,10 @@ check_modules_exist(const char* module_conf) } n[j] = s[j]; } - fatal_exit("module_conf lists module '%s' but that " - "module is not available.", n); + fatal_exit("Unknown value in module-config, module: " + "'%s'. This module is not present (not " + "compiled in); see the list of linked modules " + "with unbound -V", n); } s += strlen(names[i]); } From 8190526250c6d8bca271f3f84f74bb9f789b0a37 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 9 May 2025 16:01:41 +0200 Subject: [PATCH 119/218] - Fix #1281: forward-zone "name: ." conflicts with auth-zone "name: ." in 1.23.0, but worked in 1.22.0. --- doc/Changelog | 4 ++++ iterator/iter_fwd.c | 32 +++++++++++++++++++++----------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index e97eed9bf..fc293a5f3 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +9 May 2025: Wouter + - Fix #1281: forward-zone "name: ." conflicts with auth-zone "name: ." + in 1.23.0, but worked in 1.22.0. + 5 May 2025: Yorgos - Sync unbound and unbound-checkconf log output for unknown modules. diff --git a/iterator/iter_fwd.c b/iterator/iter_fwd.c index 5c104a0a3..5d70c6664 100644 --- a/iterator/iter_fwd.c +++ b/iterator/iter_fwd.c @@ -139,6 +139,17 @@ forwards_insert_data(struct iter_forwards* fwd, uint16_t c, uint8_t* nm, return 1; } +static struct iter_forward_zone* +fwd_zone_find(struct iter_forwards* fwd, uint16_t c, uint8_t* nm) +{ + struct iter_forward_zone key; + key.node.key = &key; + key.dclass = c; + key.name = nm; + key.namelabs = dname_count_size_labels(nm, &key.namelen); + return (struct iter_forward_zone*)rbtree_search(fwd->tree, &key); +} + /** insert new info into forward structure given dp */ static int forwards_insert(struct iter_forwards* fwd, uint16_t c, struct delegpt* dp) @@ -321,6 +332,11 @@ make_stub_holes(struct iter_forwards* fwd, struct config_file* cfg) log_err("cannot parse stub name '%s'", s->name); return 0; } + if(fwd_zone_find(fwd, LDNS_RR_CLASS_IN, dname) != NULL) { + /* Already a forward zone there. */ + free(dname); + continue; + } if(!fwd_add_stub_hole(fwd, LDNS_RR_CLASS_IN, dname)) { free(dname); log_err("out of memory"); @@ -345,6 +361,11 @@ make_auth_holes(struct iter_forwards* fwd, struct config_file* cfg) log_err("cannot parse auth name '%s'", a->name); return 0; } + if(fwd_zone_find(fwd, LDNS_RR_CLASS_IN, dname) != NULL) { + /* Already a forward zone there. */ + free(dname); + continue; + } if(!fwd_add_stub_hole(fwd, LDNS_RR_CLASS_IN, dname)) { free(dname); log_err("out of memory"); @@ -537,17 +558,6 @@ forwards_get_mem(struct iter_forwards* fwd) return s; } -static struct iter_forward_zone* -fwd_zone_find(struct iter_forwards* fwd, uint16_t c, uint8_t* nm) -{ - struct iter_forward_zone key; - key.node.key = &key; - key.dclass = c; - key.name = nm; - key.namelabs = dname_count_size_labels(nm, &key.namelen); - return (struct iter_forward_zone*)rbtree_search(fwd->tree, &key); -} - int forwards_add_zone(struct iter_forwards* fwd, uint16_t c, struct delegpt* dp, int nolock) From 4e23523d1a56ad3a16b9f2437bc8cb2e3feb7015 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 12 May 2025 14:26:47 +0200 Subject: [PATCH 120/218] Fix auth nsec3 code (#1280) - Fix NSEC3 code to not break on broken auth zones that include unsigned out of zone (above apex) data. Could lead to hang while trying to prove a wildcard answer. Reported by Dmitrii Kuvaiskii from Amazon Web Services. - Tests for NSEC3 auth zones with out of zone data. --- services/authzone.c | 28 +-- testcode/unitdname.c | 18 ++ .../auth_nsec3_ent_with_out_of_zone_data.rpl | 228 +++++++++++++++++ .../auth_nsec3_wild_with_out_of_zone_data.rpl | 234 ++++++++++++++++++ util/data/dname.c | 20 +- util/data/dname.h | 15 +- 6 files changed, 523 insertions(+), 20 deletions(-) create mode 100644 testdata/auth_nsec3_ent_with_out_of_zone_data.rpl create mode 100644 testdata/auth_nsec3_wild_with_out_of_zone_data.rpl diff --git a/services/authzone.c b/services/authzone.c index 3c3dc9ad0..7d1df032e 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -2413,14 +2413,12 @@ az_find_wildcard(struct auth_zone* z, struct query_info* qinfo, if(!dname_subdomain_c(nm, z->name)) return NULL; /* out of zone */ while((node=az_find_wildcard_domain(z, nm, nmlen))==NULL) { - /* see if we can go up to find the wildcard */ if(nmlen == z->namelen) return NULL; /* top of zone reached */ if(ce && nmlen == ce->namelen) return NULL; /* ce reached */ - if(dname_is_root(nm)) - return NULL; /* cannot go up */ - dname_remove_label(&nm, &nmlen); + if(!dname_remove_label_limit_len(&nm, &nmlen, z->namelen)) + return NULL; /* can't go up */ } return node; } @@ -2442,9 +2440,8 @@ az_find_candidate_ce(struct auth_zone* z, struct query_info* qinfo, n = az_find_name(z, nm, nmlen); /* delete labels and go up on name */ while(!n) { - if(dname_is_root(nm)) - return NULL; /* cannot go up */ - dname_remove_label(&nm, &nmlen); + if(!dname_remove_label_limit_len(&nm, &nmlen, z->namelen)) + return NULL; /* can't go up */ n = az_find_name(z, nm, nmlen); } return n; @@ -2456,8 +2453,7 @@ az_domain_go_up(struct auth_zone* z, struct auth_data* n) { uint8_t* nm = n->name; size_t nmlen = n->namelen; - while(!dname_is_root(nm)) { - dname_remove_label(&nm, &nmlen); + while(dname_remove_label_limit_len(&nm, &nmlen, z->namelen)) { if((n=az_find_name(z, nm, nmlen)) != NULL) return n; } @@ -2788,9 +2784,9 @@ az_find_nsec_cover(struct auth_zone* z, struct auth_data** node) /* but there could be glue, and if this is node, then it has no NSEC. * Go up to find nonglue (previous) NSEC-holding nodes */ while((rrset=az_domain_rrset(*node, LDNS_RR_TYPE_NSEC)) == NULL) { - if(dname_is_root(nm)) return NULL; if(nmlen == z->namelen) return NULL; - dname_remove_label(&nm, &nmlen); + if(!dname_remove_label_limit_len(&nm, &nmlen, z->namelen)) + return NULL; /* can't go up */ /* adjust *node for the nsec rrset to find in */ *node = az_find_name(z, nm, nmlen); } @@ -3018,12 +3014,9 @@ az_nsec3_find_ce(struct auth_zone* z, uint8_t** cenm, size_t* cenmlen, struct auth_data* node; while((node = az_nsec3_find_exact(z, *cenm, *cenmlen, algo, iter, salt, saltlen)) == NULL) { - if(*cenmlen == z->namelen) { - /* next step up would take us out of the zone. fail */ - return NULL; - } + if(!dname_remove_label_limit_len(cenm, cenmlen, z->namelen)) + return NULL; /* can't go up */ *no_exact_ce = 1; - dname_remove_label(cenm, cenmlen); } return node; } @@ -3340,7 +3333,8 @@ az_generate_wildcard_answer(struct auth_zone* z, struct query_info* qinfo, } else if(ce) { uint8_t* wildup = wildcard->name; size_t wilduplen= wildcard->namelen; - dname_remove_label(&wildup, &wilduplen); + if(!dname_remove_label_limit_len(&wildup, &wilduplen, z->namelen)) + return 0; /* can't go up */ if(!az_add_nsec3_proof(z, region, msg, wildup, wilduplen, msg->qinfo.qname, msg->qinfo.qname_len, 0, insert_ce, 1, 0)) diff --git a/testcode/unitdname.c b/testcode/unitdname.c index 08a2dbad7..32a9a5fdc 100644 --- a/testcode/unitdname.c +++ b/testcode/unitdname.c @@ -476,6 +476,23 @@ dname_test_removelabel(void) unit_assert( l == 1 ); } +/** test dname_remove_label_limit_len */ +static void +dname_test_removelabellimitlen(void) +{ + uint8_t* orig = (uint8_t*)"\007example\003com\000"; + uint8_t* n = orig; + size_t l = 13; + size_t lenlimit = 5; /* com.*/ + unit_show_func("util/data/dname.c", "dname_remove_label_limit_len"); + unit_assert(dname_remove_label_limit_len(&n, &l, lenlimit) == 1); + unit_assert( n == orig+8 ); + unit_assert( l == 5 ); + unit_assert(dname_remove_label_limit_len(&n, &l, lenlimit) == 0); + unit_assert( n == orig+8 ); + unit_assert( l == 5 ); +} + /** test dname_signame_label_count */ static void dname_test_sigcount(void) @@ -1024,6 +1041,7 @@ void dname_test(void) dname_test_subdomain(); dname_test_isroot(); dname_test_removelabel(); + dname_test_removelabellimitlen(); dname_test_sigcount(); dname_test_iswild(); dname_test_canoncmp(); diff --git a/testdata/auth_nsec3_ent_with_out_of_zone_data.rpl b/testdata/auth_nsec3_ent_with_out_of_zone_data.rpl new file mode 100644 index 000000000..338151565 --- /dev/null +++ b/testdata/auth_nsec3_ent_with_out_of_zone_data.rpl @@ -0,0 +1,228 @@ +; config options +server: + target-fetch-policy: "0 0 0 0 0" + +auth-zone: + name: "unbound-auth-test.nlnetlabs.nl." + ## zonefile (or none). + ## zonefile: "example.com.zone" + ## master by IP address or hostname + ## can list multiple masters, each on one line. + ## master: + ## url for http fetch + ## url: + ## queries from downstream clients get authoritative answers. + ## for-downstream: yes + for-downstream: yes + ## queries are used to fetch authoritative answers from this zone, + ## instead of unbound itself sending queries there. + ## for-upstream: yes + for-upstream: yes + ## on failures with for-upstream, fallback to sending queries to + ## the authority servers + ## fallback-enabled: no + + ## this line generates zonefile: \n"/tmp/xxx.example.com"\n + zonefile: +TEMPFILE_NAME unbound-auth-test.nlnetlabs.nl + ## this is the inline file /tmp/xxx.unbound-auth-test.nlnetlabs.nl + ## the tempfiles are deleted when the testrun is over. +TEMPFILE_CONTENTS unbound-auth-test.nlnetlabs.nl +;; Zone: unbound-auth-test.nlnetlabs.nl. +; +unbound-auth-test.nlnetlabs.nl. 3600 IN SOA ns.nlnetlabs.nl. ralph.nlnetlabs.nl. 1554201247 14400 3600 604800 3600 +unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG SOA 13 3 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. NLFcC2oet+HC+1dhT4D/2JJFIcMiRtTM81KwvT7u8ybF3iDE4bnyrILvQk8DsizpYKwk+D3J3tMC3TV5+//qFw== +;; Out of zone record that shouldn't break NSEC3 proofs. +;; There was a bug that would keep removing labels and use this out of zone +;; record. +nlnetlabs.nl. 3600 IN NS ns.nlnetlabs.nl. +; +unbound-auth-test.nlnetlabs.nl. 3600 IN NS ns.nlnetlabs.nl. +unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG NS 13 3 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. Gm0UF77ljiInG4/HZ6Tkzx7z9N45WwwmbBt9KxeN3z1BkdBLiy10Du71ZBFLP71b+USs1rv5SJQ0hteZFbl8sg== +unbound-auth-test.nlnetlabs.nl. 3600 IN DNSKEY 256 3 13 S3Da9HqpFj0pEbI8WXOdkvN3vgZ6qxNSz4XyKkmWWAG28kq5T+/lWp36DUDvnMI9wJNuixzUHtgZ6oZoAaVrPg== ;{id = 15486 (zsk), size = 256b} +unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG DNSKEY 13 3 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. 1cLFaDb6kP8KnRJujW1ieHUdS5Tgdv59TCZ+FloCRJMJBwQAow6UKAIY7HHlTb8IHTajyUrjlxX/dN8S/5VwuA== +unbound-auth-test.nlnetlabs.nl. 3600 IN NSEC3PARAM 1 0 1 - +unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG NSEC3PARAM 13 3 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. GWgtJArNpfJ4ifoinUBUVRTlkk0CMemdozhMKY13dk3EQMP0jb4g49PcTAgEP2dBUs9efttQVQQpmFPyTGfN1w== +tvdhfml24jp7cott1qijj9812qu9ibh3.unbound-auth-test.nlnetlabs.nl. 3600 IN NSEC3 1 0 1 - 41pcah2j3fr8k99gj5pveh4igrjfc871 NS SOA RRSIG DNSKEY NSEC3PARAM ;{ flags: -, from: unbound-auth-test.nlnetlabs.nl. to: b.b.unbound-auth-test.nlnetlabs.nl.} +tvdhfml24jp7cott1qijj9812qu9ibh3.unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG NSEC3 13 4 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. DzwQTaZj4j29eHXEKllIFcq4yNWA7VMqkh8+gCrBO+GEek9+hGxL6ANsU0Hv6glyBmPDeYUZcy4xy0EEj1R4hQ== +; +;; Empty nonterminal: b.unbound-auth-test.nlnetlabs.nl. +apejmh1fqds9gir0nnsf4d5gtno10tg1.unbound-auth-test.nlnetlabs.nl. 3600 IN NSEC3 1 0 1 - dbs0aj50410urbvt3ghfr644n7h06gs5 ;{ flags: -, from: b.unbound-auth-test.nlnetlabs.nl. to: c.b.unbound-auth-test.nlnetlabs.nl.} +apejmh1fqds9gir0nnsf4d5gtno10tg1.unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG NSEC3 13 4 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. m9B0W8xDZF6ml/m8OujrZZBiF1O0wAeKciK/5FMT/hCjHR0hMrbXBPg/ZntpVJD/Pko2HcBvWKu87U721yTHyQ== +; +;; Empty nonterminal: a.b.unbound-auth-test.nlnetlabs.nl. +toqivctpt4pdcp5g19neqt19fvtgbgeu.unbound-auth-test.nlnetlabs.nl. 3600 IN NSEC3 1 0 1 - tvdhfml24jp7cott1qijj9812qu9ibh3 ;{ flags: -, from: a.b.unbound-auth-test.nlnetlabs.nl. to: unbound-auth-test.nlnetlabs.nl.} +toqivctpt4pdcp5g19neqt19fvtgbgeu.unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG NSEC3 13 4 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. Jr1oPPs+DGBVV13n4gG4AGVFsleItluLbtCIyQDcYZEA+e5JMkrLzfW3rXqXaUSUauR4iEu5FmTfs4GTsumdUw== +; +*.a.b.unbound-auth-test.nlnetlabs.nl. 3600 IN TXT "*.a.b" +*.a.b.unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG TXT 13 5 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. NrMUaNzZp88lXit/HLL/iDBHspDSfoM//K+/0VwUYRZjmVJQQHCHtHBGgR4NgrLi3ffvCAWq2LNGxDm+YMSl3g== +jrtu61ssgd18lfjglqrbbs5b2vmbh6cl.unbound-auth-test.nlnetlabs.nl. 3600 IN NSEC3 1 0 1 - k8r2bchsbehs5dbu5d6ivjfnmjb3jc8s TXT RRSIG ;{ flags: -, from: *.a.b.unbound-auth-test.nlnetlabs.nl. to: *.c.b.unbound-auth-test.nlnetlabs.nl.} +jrtu61ssgd18lfjglqrbbs5b2vmbh6cl.unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG NSEC3 13 4 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. kLIhE9+iz1OybJwXbtRJZst+Mk5u4OAtpZGWSwJUfqD6dXAk+h6msKAR18jpPeL7cCjXjIAKIv3x4oYRkl+uKw== +; +;; Empty nonterminal: b.b.unbound-auth-test.nlnetlabs.nl. +41pcah2j3fr8k99gj5pveh4igrjfc871.unbound-auth-test.nlnetlabs.nl. 3600 IN NSEC3 1 0 1 - apejmh1fqds9gir0nnsf4d5gtno10tg1 ;{ flags: -, from: b.b.unbound-auth-test.nlnetlabs.nl. to: b.unbound-auth-test.nlnetlabs.nl.} +41pcah2j3fr8k99gj5pveh4igrjfc871.unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG NSEC3 13 4 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. XlIjnuF313w0GXn6vymrAcsyuxZSaN6IShFjxQ5T2HUFePHBNvtRkL+TtMQZNlR8nTR3+MWcON0cOZIGjVCCjg== +; +*.b.b.unbound-auth-test.nlnetlabs.nl. 3600 IN TXT "*.b.b" +*.b.b.unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG TXT 13 5 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. FkS3ceWpoHyOKaa8OtywIl148Bwo0vkzBd263vqYe0puhuRa6IvNEk5ERdwfWt9eNEq+6IlizPT/dYxA2fXYXA== +ft7dasbom0copm9e2ak9k151dj08kjfs.unbound-auth-test.nlnetlabs.nl. 3600 IN NSEC3 1 0 1 - jrtu61ssgd18lfjglqrbbs5b2vmbh6cl TXT RRSIG ;{ flags: -, from: *.b.b.unbound-auth-test.nlnetlabs.nl. to: *.a.b.unbound-auth-test.nlnetlabs.nl.} +ft7dasbom0copm9e2ak9k151dj08kjfs.unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG NSEC3 13 4 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. 5QhLGohTRLQSGC8vstzDjqcwfrbOnLUG2OelSjvsZFy1smsWUxJBCQXQdx1+JX7xamZHlZESQtS+cELuZUqpvA== +; +;; Empty nonterminal: c.b.unbound-auth-test.nlnetlabs.nl. +dbs0aj50410urbvt3ghfr644n7h06gs5.unbound-auth-test.nlnetlabs.nl. 3600 IN NSEC3 1 0 1 - ft7dasbom0copm9e2ak9k151dj08kjfs ;{ flags: -, from: c.b.unbound-auth-test.nlnetlabs.nl. to: *.b.b.unbound-auth-test.nlnetlabs.nl.} +dbs0aj50410urbvt3ghfr644n7h06gs5.unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG NSEC3 13 4 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. hjk1foJWW68JK3O1Ktf0ZogoXVrMDw3mHVBBYTrpaBKX1gWR5icmJiOCYZWYx3z88PUnGkfH+kx4oDUjioqN+Q== +; +*.c.b.unbound-auth-test.nlnetlabs.nl. 3600 IN TXT "*.c.b" +*.c.b.unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG TXT 13 5 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. b7rFR5tlx5Y5SQqNdYBtfD6DrkNx9h79GCmnZfWrUzRz+A256k2v08IPRJDK+WxEHuYHjfNnVWxjRr9M1OW2Iw== +k8r2bchsbehs5dbu5d6ivjfnmjb3jc8s.unbound-auth-test.nlnetlabs.nl. 3600 IN NSEC3 1 0 1 - toqivctpt4pdcp5g19neqt19fvtgbgeu TXT RRSIG ;{ flags: -, from: *.c.b.unbound-auth-test.nlnetlabs.nl. to: a.b.unbound-auth-test.nlnetlabs.nl.} +k8r2bchsbehs5dbu5d6ivjfnmjb3jc8s.unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG NSEC3 13 4 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. 34BS1ajedCNdfXgUfxTyiAK1ichfFLshhJ3TnfplrUps0UsZaQLEG+EIlP4wTBtro2c6V8YCSmOuxuce4gYoDw== +; +TEMPFILE_END + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test authority zone with NSEC3 empty nonterminal +; with exact match NSEC3 in existence (eg. not a CE-proof) + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION AUTHORITY +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END +RANGE_END + +; a.gtld-servers.net. +RANGE_BEGIN 0 100 + ADDRESS 192.5.6.30 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION ANSWER +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.44 +ENTRY_END +RANGE_END + +; ns.example.net. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.44 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.net. IN NS +SECTION ANSWER +example.net. IN NS ns.example.net. +SECTION ADDITIONAL +ns.example.net. IN A 1.2.3.44 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.example.net. IN A +SECTION ANSWER +ns.example.net. IN A 1.2.3.44 +SECTION AUTHORITY +example.net. IN NS ns.example.net. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.example.net. IN AAAA +SECTION AUTHORITY +example.net. IN NS ns.example.net. +SECTION ADDITIONAL +www.example.net. IN A 1.2.3.44 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION ANSWER +example.com. IN NS ns.example.net. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +ENTRY_END +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +a.b.unbound-auth-test.nlnetlabs.nl. IN TXT +ENTRY_END + +; recursion happens here. +STEP 20 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA DO NOERROR +SECTION QUESTION +a.b.unbound-auth-test.nlnetlabs.nl. IN TXT +SECTION ANSWER +SECTION AUTHORITY +unbound-auth-test.nlnetlabs.nl. 3600 IN SOA ns.nlnetlabs.nl. ralph.nlnetlabs.nl. 1554201247 14400 3600 604800 3600 +unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG SOA 13 3 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. NLFcC2oet+HC+1dhT4D/2JJFIcMiRtTM81KwvT7u8ybF3iDE4bnyrILv Qk8DsizpYKwk+D3J3tMC3TV5+//qFw== +toqivctpt4pdcp5g19neqt19fvtgbgeu.unbound-auth-test.nlnetlabs.nl. 3600 IN NSEC3 1 0 1 - TVDHFML24JP7COTT1QIJJ9812QU9IBH3 +toqivctpt4pdcp5g19neqt19fvtgbgeu.unbound-auth-test.nlnetlabs.nl. 3600 IN RRSIG NSEC3 13 4 3600 20190430103407 20190402103407 15486 unbound-auth-test.nlnetlabs.nl. Jr1oPPs+DGBVV13n4gG4AGVFsleItluLbtCIyQDcYZEA+e5JMkrLzfW3 rXqXaUSUauR4iEu5FmTfs4GTsumdUw== +ENTRY_END + +SCENARIO_END diff --git a/testdata/auth_nsec3_wild_with_out_of_zone_data.rpl b/testdata/auth_nsec3_wild_with_out_of_zone_data.rpl new file mode 100644 index 000000000..8c5a00861 --- /dev/null +++ b/testdata/auth_nsec3_wild_with_out_of_zone_data.rpl @@ -0,0 +1,234 @@ +; config options +server: + target-fetch-policy: "0 0 0 0 0" + +auth-zone: + name: "test-ns-signed.dev.internet.nl." + ## zonefile (or none). + ## zonefile: "example.com.zone" + ## master by IP address or hostname + ## can list multiple masters, each on one line. + ## master: + ## url for http fetch + ## url: + ## queries from downstream clients get authoritative answers. + ## for-downstream: yes + for-downstream: yes + ## queries are used to fetch authoritative answers from this zone, + ## instead of unbound itself sending queries there. + ## for-upstream: yes + for-upstream: yes + ## on failures with for-upstream, fallback to sending queries to + ## the authority servers + ## fallback-enabled: no + + ## this line generates zonefile: \n"/tmp/xxx.example.com"\n + zonefile: +TEMPFILE_NAME test-ns-signed.dev.internet.nl + ## this is the inline file /tmp/xxx.test-ns-signed.dev.internet.nl + ## the tempfiles are deleted when the testrun is over. +TEMPFILE_CONTENTS test-ns-signed.dev.internet.nl +test-ns-signed.dev.internet.nl. 3600 IN SOA ns.nlnetlabs.nl. ralph.nlnetlabs.nl. 4 14400 3600 604800 3600 +test-ns-signed.dev.internet.nl. 3600 IN RRSIG SOA 8 4 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. ybb0Hc7NC+QOFEEv4cX2+Umlk+miiOAHmeP2Uwvg6lqfxkk+3g7yWBEKMinXjLKz0odWZ6fki6M/3yBPQX8SV0OCRY5gYvAHAjbxAIHozIM+5iwOkRQhNF1DRgQ3BLjL93f6T5e5Z4y1812iOpu4GYswXW/UTOZACXz2UiaCPAg= +;; Out of zone record that shouldn't break NSEC3 proofs. +;; There was a bug that would keep removing labels and use this out of zone +;; record. +dev.internet.nl. 3600 IN NS ns.test-ns-signed.dev.internet.nl. +test-ns-signed.dev.internet.nl. 3600 IN NS ns.test-ns-signed.dev.internet.nl. +test-ns-signed.dev.internet.nl. 3600 IN RRSIG NS 8 4 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. KqiwTF3hKm1ZHGbgx6MVzZYHlS1p7+Xrikx4izMHFbWiD6ki6lrJBJsnH9j/hH1cwHxjXslOeJh0hdBdbn8la0meZPsebOyUbEjoLPzRLzKNLDBuA4BUJnRGQJy21CX7XooXAMAmR8YFipO8CojI9EogU2m2o9YkfbpacFWQoTk= +test-ns-signed.dev.internet.nl. 3600 IN DNSKEY 256 3 8 AwEAAc6c8tpMXBSOFLu/9n4aUUDK43wN4B7A2UDqZi0IOkyptxWCFghleyZeeN5uq6p9MoUt8lS73mFmIYC0ux5zBO3uVaJQ9u+00qRAEVg/RgBwa58y2f/zNtFV/f7mBSPcPTiEjUh0bwHSiTvUn/8JkrvjyAcbQMO0YOsRof5q6tzl ;{id = 32784 (zsk), size = 1024b} +test-ns-signed.dev.internet.nl. 3600 IN DNSKEY 257 3 8 AwEAAdC0hBJP1U8lbZ6JFXn0ouK6VipiraN7I8oog62SuEd/fqAupys7A/Ih6WK/UoJorjlnccEL8euNMaS4kNogvoBrFx8ciIWKcbot5mtwc4WDr3cnR+HIZNCUFVkIxsMqE7HCD0yn0zhkB60shED+ZHs8zpyU+cjnsOSizxOnIY+F ;{id = 54502 (ksk), size = 1024b} +test-ns-signed.dev.internet.nl. 3600 IN RRSIG DNSKEY 8 4 3600 20190205132351 20190108132351 54502 test-ns-signed.dev.internet.nl. X3qN+plfjf45FA4pr/tcUqUCR9ajDqwtNe4TS19WOJogVL/Gf/N5/ToOCrs3s+a7VrJl58WvSJquDM8xAS8f4oJggKgHFhopce8tMTGRxkRvJo4y+tt3vCveh/zjHLAnbOaBGA4CJ/IPhRqzHzcX/SjSv0EACWd6XpQIWogRv6c= +test-ns-signed.dev.internet.nl. 3600 IN NSEC3PARAM 1 0 1 - +test-ns-signed.dev.internet.nl. 3600 IN RRSIG NSEC3PARAM 8 4 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. A/1xUGO46uIz+9vjPGfWVD99akwU9bd/UlnVG9LPfoTzG7TMWSoZ4ksg8k8ub8K1TrkDmQokNHSW0Gt6qwoRh17c+p1h/SFlDVL83wgTc4NqG43OQjgGU9RV035XU+VESlO3lavifhlu8rHWBJTlhiXcMGq6H+zvoz4sx9p5GNM= +93stp7o7i5n9gb83uu7vv6h8qltk14ig.test-ns-signed.dev.internet.nl. 3600 IN NSEC3 1 0 1 - fee0c2kfhi6bnljce6vehaenqq3pbupu NS SOA RRSIG DNSKEY NSEC3PARAM +93stp7o7i5n9gb83uu7vv6h8qltk14ig.test-ns-signed.dev.internet.nl. 3600 IN RRSIG NSEC3 8 5 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. YoTRDQ7sSvERcY1WwAH4oRRR7DmaAwA8/H70jdMeSU4wsnM/VM03kDcc2sgq5edmHiZoTWnq7nEb/1Y7Ro0YrqTUQdYFZvXi6UjZQrKI9nqAGnhdXZWlZJHmYpn2+2Emd+bYHkwvKaPnfnnKjUoGVBH8Hly0HBYKPUF1/viquB0= +kl94uofq16t2vlq0bmampf6e4o9k5hbi.test-ns-signed.dev.internet.nl. 3600 IN NSEC3 1 0 1 - 7ag3p2pfrvq09dpn63cvga8ub1rnrrg1 +kl94uofq16t2vlq0bmampf6e4o9k5hbi.test-ns-signed.dev.internet.nl. 3600 IN RRSIG NSEC3 8 5 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. NI5zJ/k1kPVZ1abms5OoME/wazb77Ltduyk6ZevAnt4tKydZYwSsjEd0Ixknw9xnakCABn5rAYEXctARN0KCwCkNHR7TYlTAJT14hlDYjbad2u2HT9L1kzAnfj3BeLZl/LRADeMbTtzrkTSF3Dnezurb94fMnUnKt2hPfQfj560= +fee0c2kfhi6bnljce6vehaenqq3pbupu.test-ns-signed.dev.internet.nl. 3600 IN NSEC3 1 0 1 - i6pi4e3o98e7vtkpjfhqn7g77d3mjcnv +fee0c2kfhi6bnljce6vehaenqq3pbupu.test-ns-signed.dev.internet.nl. 3600 IN RRSIG NSEC3 8 5 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. WIb3ISP1nlafbyWoWa4z7sG5IS+V86PyvEMHdD/64hgsFkrCu483XK7VNnBz28SL/631JXA1R19O+UxeWhTUyctp8QSt6cEZcMPY8b7yG97rNFNvhSw75rSXXt+JwgIYHPHQV5oqPtVmEpQM5SfJd+hs+Nn1bJcWB3UaESNNAMQ= +*.a.b.test-ns-signed.dev.internet.nl. 3600 IN TXT "a" +*.a.b.test-ns-signed.dev.internet.nl. 3600 IN RRSIG TXT 8 6 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. eNcJkQXdTO1z21od0sXbgqtABhhr/9tNC/Zx8zYbhXkfj7rufN71yk9xqgu6TG0MeJV26ISrqIGRVFJFmTRvO1LLxoKkEPhqe+08nqRztxXZajCV+dDeFoGIDcXJg6tAxB+MJznkKDtZPpIWvyt1WwdYfcMrGtE9AmR3K1/P/xE= +7ag3p2pfrvq09dpn63cvga8ub1rnrrg1.test-ns-signed.dev.internet.nl. 3600 IN NSEC3 1 0 1 - 93stp7o7i5n9gb83uu7vv6h8qltk14ig TXT RRSIG +7ag3p2pfrvq09dpn63cvga8ub1rnrrg1.test-ns-signed.dev.internet.nl. 3600 IN RRSIG NSEC3 8 5 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. gtxoiTa3FRUqoRLvkWSxmWQ+DfijVd26gpKH3+GmGIcNB/sr/Cf8kERRwVVHvgzYIcvdJcys5b2LUXnZJwcdAlx7efZPWgNZzWxJrw6ES25LCWJOrp31isWn9FlAZGIbnpyEXxD2apBSmtyPnKbTgU6lHHS9jrsYHu4G8Zouv3k= +ns.test-ns-signed.dev.internet.nl. 3600 IN A 185.49.141.11 +ns.test-ns-signed.dev.internet.nl. 3600 IN RRSIG A 8 5 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. F9sXEVAmlRn+/84WbuvegiCwstNxMDMQLl0Obv2CTPpee4U6psbmXrlzczjjjkE6aLjsIHYdcXCzEWTrmukT+V9jzaGPRJvxNvC0ASWyzggAoh0Z++Hl4cVa9587o6I9ODayehFI9Pgdem+RVdb4zlWuzi9FmKXgeTlgWN54tPg= +ns.test-ns-signed.dev.internet.nl. 3600 IN AAAA 2a04:b900:0:100::11 +ns.test-ns-signed.dev.internet.nl. 3600 IN RRSIG AAAA 8 5 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. F1XRrx/QgfzJ1RS7d0m23QoIPx1G8WL1SrlTOm7pk5vWTL07w7HEw2TETblkjnitJGKfN9ebsIum/cDPUZc3UqLkguP2UCWpePnlllTJuwmG0Z+wyINIR4xF4PQlqttvzThBkD2JKWb/o0W8dQyXTj+jJ1vCZ0NjjA2N4+iJIQE= +i6pi4e3o98e7vtkpjfhqn7g77d3mjcnv.test-ns-signed.dev.internet.nl. 3600 IN NSEC3 1 0 1 - kl94uofq16t2vlq0bmampf6e4o9k5hbi A AAAA RRSIG +i6pi4e3o98e7vtkpjfhqn7g77d3mjcnv.test-ns-signed.dev.internet.nl. 3600 IN RRSIG NSEC3 8 5 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. xLysIqn3r3rdHE3GvwVjZwUyuFClhkhgrQdwyc66RuHKE3MfSuhVr9cHTCJzhipF5TwQTbUpLOr74r99bzdiIY8Xkgjy2M0nc76v1ObSGJdPPjGTevbhDOnavUURwOR/q0NqqO2iPrgFjOVMZ+8uwRJtCty2iAVZfVG+qDzs8hU= +TEMPFILE_END + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test authority zone with NSEC3 wildcard + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION AUTHORITY +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END +RANGE_END + +; a.gtld-servers.net. +RANGE_BEGIN 0 100 + ADDRESS 192.5.6.30 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION ANSWER +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.44 +ENTRY_END +RANGE_END + +; ns.example.net. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.44 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.net. IN NS +SECTION ANSWER +example.net. IN NS ns.example.net. +SECTION ADDITIONAL +ns.example.net. IN A 1.2.3.44 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.example.net. IN A +SECTION ANSWER +ns.example.net. IN A 1.2.3.44 +SECTION AUTHORITY +example.net. IN NS ns.example.net. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.example.net. IN AAAA +SECTION AUTHORITY +example.net. IN NS ns.example.net. +SECTION ADDITIONAL +www.example.net. IN A 1.2.3.44 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION ANSWER +example.com. IN NS ns.example.net. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +ENTRY_END +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +something.a.b.test-ns-signed.dev.internet.nl. IN TXT +ENTRY_END + +; recursion happens here. +STEP 20 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA DO NOERROR +SECTION QUESTION +something.a.b.test-ns-signed.dev.internet.nl. IN TXT +SECTION ANSWER +something.a.b.test-ns-signed.dev.internet.nl. IN TXT "a" +something.a.b.test-ns-signed.dev.internet.nl. 3600 IN RRSIG TXT 8 6 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. eNcJkQXdTO1z21od0sXbgqtABhhr/9tNC/Zx8zYbhXkfj7rufN71yk9xqgu6TG0MeJV26ISrqIGRVFJFmTRvO1LLxoKkEPhqe+08nqRztxXZajCV+dDeFoGIDcXJg6tAxB+MJznkKDtZPpIWvyt1WwdYfcMrGtE9AmR3K1/P/xE= +SECTION AUTHORITY +i6pi4e3o98e7vtkpjfhqn7g77d3mjcnv.test-ns-signed.dev.internet.nl. 3600 IN NSEC3 1 0 1 - KL94UOFQ16T2VLQ0BMAMPF6E4O9K5HBI A AAAA RRSIG +i6pi4e3o98e7vtkpjfhqn7g77d3mjcnv.test-ns-signed.dev.internet.nl. 3600 IN RRSIG NSEC3 8 5 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. xLysIqn3r3rdHE3GvwVjZwUyuFClhkhgrQdwyc66RuHKE3MfSuhVr9cHTCJzhipF5TwQTbUpLOr74r99bzdiIY8Xkgjy2M0nc76v1ObSGJdPPjGTevbhDOnavUURwOR/q0NqqO2iPrgFjOVMZ+8uwRJtCty2iAVZfVG+qDzs8hU= +ENTRY_END + +; Check that the reply for a wildcard nodata answer contains the NSEC3s. +; qname denial NSEC3, closest encloser NSEC3, and type bitmap NSEC3. +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +something.a.b.test-ns-signed.dev.internet.nl. IN AAAA +ENTRY_END + +STEP 40 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA DO NOERROR +SECTION QUESTION +something.a.b.test-ns-signed.dev.internet.nl. IN AAAA +SECTION ANSWER +SECTION AUTHORITY +test-ns-signed.dev.internet.nl. 3600 IN SOA ns.nlnetlabs.nl. ralph.nlnetlabs.nl. 4 14400 3600 604800 3600 +test-ns-signed.dev.internet.nl. 3600 IN RRSIG SOA 8 4 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. ybb0Hc7NC+QOFEEv4cX2+Umlk+miiOAHmeP2Uwvg6lqfxkk+3g7yWBEKMinXjLKz0odWZ6fki6M/3yBPQX8SV0OCRY5gYvAHAjbxAIHozIM+5iwOkRQhNF1DRgQ3BLjL93f6T5e5Z4y1812iOpu4GYswXW/UTOZACXz2UiaCPAg= ;{id = 32784} +7ag3p2pfrvq09dpn63cvga8ub1rnrrg1.test-ns-signed.dev.internet.nl. 3600 IN NSEC3 1 0 1 - 93stp7o7i5n9gb83uu7vv6h8qltk14ig TXT RRSIG +7ag3p2pfrvq09dpn63cvga8ub1rnrrg1.test-ns-signed.dev.internet.nl. 3600 IN RRSIG NSEC3 8 5 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. gtxoiTa3FRUqoRLvkWSxmWQ+DfijVd26gpKH3+GmGIcNB/sr/Cf8kERRwVVHvgzYIcvdJcys5b2LUXnZJwcdAlx7efZPWgNZzWxJrw6ES25LCWJOrp31isWn9FlAZGIbnpyEXxD2apBSmtyPnKbTgU6lHHS9jrsYHu4G8Zouv3k= ;{id = 32784} +fee0c2kfhi6bnljce6vehaenqq3pbupu.test-ns-signed.dev.internet.nl. 3600 IN NSEC3 1 0 1 - i6pi4e3o98e7vtkpjfhqn7g77d3mjcnv +fee0c2kfhi6bnljce6vehaenqq3pbupu.test-ns-signed.dev.internet.nl. 3600 IN RRSIG NSEC3 8 5 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. WIb3ISP1nlafbyWoWa4z7sG5IS+V86PyvEMHdD/64hgsFkrCu483XK7VNnBz28SL/631JXA1R19O+UxeWhTUyctp8QSt6cEZcMPY8b7yG97rNFNvhSw75rSXXt+JwgIYHPHQV5oqPtVmEpQM5SfJd+hs+Nn1bJcWB3UaESNNAMQ= ;{id = 32784} +i6pi4e3o98e7vtkpjfhqn7g77d3mjcnv.test-ns-signed.dev.internet.nl. 3600 IN NSEC3 1 0 1 - kl94uofq16t2vlq0bmampf6e4o9k5hbi A AAAA RRSIG +i6pi4e3o98e7vtkpjfhqn7g77d3mjcnv.test-ns-signed.dev.internet.nl. 3600 IN RRSIG NSEC3 8 5 3600 20190205132351 20190108132351 32784 test-ns-signed.dev.internet.nl. xLysIqn3r3rdHE3GvwVjZwUyuFClhkhgrQdwyc66RuHKE3MfSuhVr9cHTCJzhipF5TwQTbUpLOr74r99bzdiIY8Xkgjy2M0nc76v1ObSGJdPPjGTevbhDOnavUURwOR/q0NqqO2iPrgFjOVMZ+8uwRJtCty2iAVZfVG+qDzs8hU= ;{id = 32784} +ENTRY_END + +SCENARIO_END diff --git a/util/data/dname.c b/util/data/dname.c index f08760e2f..2fe7b171d 100644 --- a/util/data/dname.c +++ b/util/data/dname.c @@ -728,7 +728,7 @@ dname_is_root(uint8_t* dname) return (len == 0); } -void +void dname_remove_label(uint8_t** dname, size_t* len) { size_t lablen; @@ -742,7 +742,23 @@ dname_remove_label(uint8_t** dname, size_t* len) *dname += lablen+1; } -void +int +dname_remove_label_limit_len(uint8_t** dname, size_t* len, size_t lenlimit) +{ + size_t lablen; + log_assert(dname && *dname && len); + lablen = (*dname)[0]; + log_assert(!LABEL_IS_PTR(lablen)); + log_assert(*len > lablen); + if(lablen == 0) + return 0; /* do not modify root label */ + if(*len - (lablen + 1) < lenlimit) return 0; + *len -= lablen+1; + *dname += lablen+1; + return 1; +} + +void dname_remove_labels(uint8_t** dname, size_t* len, int n) { int i; diff --git a/util/data/dname.h b/util/data/dname.h index 6e4cf7ea3..e4a0313ac 100644 --- a/util/data/dname.h +++ b/util/data/dname.h @@ -262,10 +262,23 @@ int dname_is_root(uint8_t* dname); * Snip off first label from a dname, returning the parent zone. * @param dname: from what to strip off. uncompressed wireformat. * @param len: length, adjusted to become less. - * return stripped off, or "." if input was ".". + * return dname stripped off, or "." if input was ".". */ void dname_remove_label(uint8_t** dname, size_t* len); +/** + * Same as dname_remove_label but fails if removal would surpass lenlimit. + * If no failure, + * snip off first label from a dname, returning the parent zone. + * @param dname: from what to strip off. uncompressed wireformat. + * @param len: length, adjusted to become less. + * @param lenlimit: length limit that we can't surpass (usually the zone apex). + * return + * o 1, and dname stripped off, or "." if input was ".", else + * o 0, if going up would surpass lenlimit. + */ +int dname_remove_label_limit_len(uint8_t** dname, size_t* len, size_t lenlimit); + /** * Snip off first N labels from a dname, returning the parent zone. * @param dname: from what to strip off. uncompressed wireformat. From 8e1deede08bfbc407943b7b8c35f64a92f3e4f0b Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Mon, 12 May 2025 14:29:11 +0200 Subject: [PATCH 121/218] Changelog entry for #1280: - Merge #1280: Fix auth nsec3 code. Fixes NSEC3 code to not break on broken auth zones that include unsigned out of zone (above apex) data. Could lead to hang while trying to prove a wildcard answer. --- doc/Changelog | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index fc293a5f3..e86095860 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,8 @@ +12 May 2025: Yorgos + - Merge #1280: Fix auth nsec3 code. Fixes NSEC3 code to not break on + broken auth zones that include unsigned out of zone (above apex) + data. Could lead to hang while trying to prove a wildcard answer. + 9 May 2025: Wouter - Fix #1281: forward-zone "name: ." conflicts with auth-zone "name: ." in 1.23.0, but worked in 1.22.0. From 21e32784001ba993908ef71f5df8598cbf062235 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Mon, 12 May 2025 14:57:42 +0200 Subject: [PATCH 122/218] - Fix #1283: Unsafe usage of atoi() while parsing the configuration file. --- doc/Changelog | 4 ++++ testcode/unitmain.c | 24 ++++++++++++++++++++++++ util/net_help.c | 9 ++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index e86095860..8d4a93ff6 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -3,6 +3,10 @@ broken auth zones that include unsigned out of zone (above apex) data. Could lead to hang while trying to prove a wildcard answer. +12 May 2025: Wouter + - Fix #1283: Unsafe usage of atoi() while parsing the configuration + file. + 9 May 2025: Wouter - Fix #1281: forward-zone "name: ." conflicts with auth-zone "name: ." in 1.23.0, but worked in 1.22.0. diff --git a/testcode/unitmain.c b/testcode/unitmain.c index 334c1af93..07c016d7b 100644 --- a/testcode/unitmain.c +++ b/testcode/unitmain.c @@ -205,6 +205,8 @@ net_test(void) unit_assert(memcmp(&a6.sin6_addr, "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\000", 16) == 0); addr_mask((struct sockaddr_storage*)&a6, l6, 64); unit_assert(memcmp(&a6.sin6_addr, "\377\377\377\377\377\377\377\377\000\000\000\000\000\000\000\000", 16) == 0); + /* Check that negative value in net is not problematic. */ + addr_mask((struct sockaddr_storage*)&a6, l6, -100); addr_mask((struct sockaddr_storage*)&a6, l6, 0); unit_assert(memcmp(&a6.sin6_addr, "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", 16) == 0); } @@ -266,6 +268,28 @@ net_test(void) (struct sockaddr_storage*)&b6, i, l6) == i); } } + /* test netblockstrtoaddr */ + unit_show_func("util/net_help.c", "netblockstrtoaddr"); + if(1) { + struct sockaddr_storage a; + socklen_t alen = 0; + int net = 0, res; + char astr[128]; + memset(&a, 0, sizeof(a)); + + res = netblockstrtoaddr("1.2.3.0/24", 53, &a, &alen, &net); + unit_assert(res!=0 && net == 24); + addr_to_str(&a, alen, astr, sizeof(astr)); + unit_assert(strcmp(astr, "1.2.3.0") == 0); + unit_assert(ntohs(((struct sockaddr_in*)&a)->sin_port)==53); + + res = netblockstrtoaddr("2001:DB8:33:44::/64", 53, + &a, &alen, &net); + unit_assert(res!=0 && net == 64); + addr_to_str(&a, alen, astr, sizeof(astr)); + unit_assert(strcmp(astr, "2001:db8:33:44::") == 0); + unit_assert(ntohs(((struct sockaddr_in6*)&a)->sin6_port)==53); + } /* test sockaddr_cmp_addr */ unit_show_func("util/net_help.c", "sockaddr_cmp_addr"); if(1) { diff --git a/util/net_help.c b/util/net_help.c index 8eca6b757..a50737dd8 100644 --- a/util/net_help.c +++ b/util/net_help.c @@ -317,6 +317,11 @@ int netblockstrtoaddr(const char* str, int port, struct sockaddr_storage* addr, log_err("cannot parse netblock: '%s'", str); return 0; } + if(*net < 0) { + log_err("netblock value %d is negative in: '%s'", + *net, str); + return 0; + } strlcpy(buf, str, sizeof(buf)); s = strchr(buf, '/'); if(s) *s = 0; @@ -430,6 +435,8 @@ int netblockdnametoaddr(uint8_t* dname, size_t dnamelen, *net = atoi(buff); if(*net == 0 && strcmp(buff, "0") != 0) return 0; + if(*net < 0) + return 0; dname += nlablen; dname++; if(!ipdnametoaddr(dname, dnamelen-1-nlablen, addr, addrlen, af)) @@ -797,7 +804,7 @@ addr_mask(struct sockaddr_storage* addr, socklen_t len, int net) s = (uint8_t*)&((struct sockaddr_in*)addr)->sin_addr; max = 32; } - if(net >= max) + if(net >= max || net < 0) return; for(i=net/8+1; i Date: Tue, 13 May 2025 11:00:23 +0200 Subject: [PATCH 123/218] - Fix #1284: NULL pointer deref in az_find_nsec_cover() (latent bug) by adding a log_assert() to safeguard future development. --- doc/Changelog | 4 ++++ services/authzone.c | 19 ++++++++----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 8d4a93ff6..3b169e793 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +13 May 2025: Yorgos + - Fix #1284: NULL pointer deref in az_find_nsec_cover() (latent bug) + by adding a log_assert() to safeguard future development. + 12 May 2025: Yorgos - Merge #1280: Fix auth nsec3 code. Fixes NSEC3 code to not break on broken auth zones that include unsigned out of zone (above apex) diff --git a/services/authzone.c b/services/authzone.c index 7d1df032e..40530d3a5 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -2767,21 +2767,18 @@ az_change_dnames(struct dns_msg* msg, uint8_t* oldname, uint8_t* newname, } } -/** find NSEC record covering the query */ +/** find NSEC record covering the query, with the given node in the zone */ static struct auth_rrset* az_find_nsec_cover(struct auth_zone* z, struct auth_data** node) { - uint8_t* nm = (*node)->name; - size_t nmlen = (*node)->namelen; + uint8_t* nm; + size_t nmlen; struct auth_rrset* rrset; + log_assert(*node); /* we already have a node when calling this */ + nm = (*node)->name; + nmlen = (*node)->namelen; /* find the NSEC for the smallest-or-equal node */ - /* if node == NULL, we did not find a smaller name. But the zone - * name is the smallest name and should have an NSEC. So there is - * no NSEC to return (for a properly signed zone) */ - /* for empty nonterminals, the auth-data node should not exist, - * and thus we don't need to go rbtree_previous here to find - * a domain with an NSEC record */ - /* but there could be glue, and if this is node, then it has no NSEC. + /* But there could be glue, and then it has no NSEC. * Go up to find nonglue (previous) NSEC-holding nodes */ while((rrset=az_domain_rrset(*node, LDNS_RR_TYPE_NSEC)) == NULL) { if(nmlen == z->namelen) return NULL; @@ -3393,7 +3390,7 @@ az_generate_answer_with_node(struct auth_zone* z, struct query_info* qinfo, } /** Generate answer without an existing-node that we can use. - * So it'll be a referral, DNAME or nxdomain */ + * So it'll be a referral, DNAME, notype, wildcard or nxdomain */ static int az_generate_answer_nonexistnode(struct auth_zone* z, struct query_info* qinfo, struct regional* region, struct dns_msg* msg, struct auth_data* ce, From 9152c914af4c1281c9b406f130191d5223330f94 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 13 May 2025 11:02:58 +0200 Subject: [PATCH 124/218] - Fix #1282: log-destaddr fail on long ipv6 addresses. --- doc/Changelog | 1 + util/data/msgreply.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 3b169e793..c44bf4f22 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,7 @@ 13 May 2025: Yorgos - Fix #1284: NULL pointer deref in az_find_nsec_cover() (latent bug) by adding a log_assert() to safeguard future development. + - Fix #1282: log-destaddr fail on long ipv6 addresses. 12 May 2025: Yorgos - Merge #1280: Fix auth nsec3 code. Fixes NSEC3 code to not break on diff --git a/util/data/msgreply.c b/util/data/msgreply.c index e98dce133..35d768f9a 100644 --- a/util/data/msgreply.c +++ b/util/data/msgreply.c @@ -984,14 +984,14 @@ log_reply_info(enum verbosity_value v, struct query_info *qinf, if(daddr->ss_family == AF_INET6) { struct sockaddr_in6 *d = (struct sockaddr_in6 *)daddr; if(inet_ntop(d->sin6_family, &d->sin6_addr, da, - sizeof(*d)) == 0) + sizeof(da)) == 0) snprintf(dest_buf, sizeof(dest_buf), "(inet_ntop_error)"); port = ntohs(d->sin6_port); } else if(daddr->ss_family == AF_INET) { struct sockaddr_in *d = (struct sockaddr_in *)daddr; if(inet_ntop(d->sin_family, &d->sin_addr, da, - sizeof(*d)) == 0) + sizeof(da)) == 0) snprintf(dest_buf, sizeof(dest_buf), "(inet_ntop_error)"); port = ntohs(d->sin_port); From 03772d10fb6840c517f1fff63180797eef8290ef Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 13 May 2025 15:04:32 +0200 Subject: [PATCH 125/218] - Change default for so-sndbuf to 1m, to mitigate a cross-layer issue where the UDP socket send buffers are exhausted waiting for ARP/NDP resolution. Thanks to Reflyable for the report. --- doc/Changelog | 5 +++++ doc/example.conf.in | 2 +- doc/unbound.conf.5.in | 13 ++++++++----- services/listen_dnsport.c | 3 ++- util/config_file.c | 2 +- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index c44bf4f22..b7bcabb6d 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -3,6 +3,11 @@ by adding a log_assert() to safeguard future development. - Fix #1282: log-destaddr fail on long ipv6 addresses. +13 May 2025: Wouter + - Change default for so-sndbuf to 1m, to mitigate a cross-layer + issue where the UDP socket send buffers are exhausted waiting + for ARP/NDP resolution. Thanks to Reflyable for the report. + 12 May 2025: Yorgos - Merge #1280: Fix auth nsec3 code. Fixes NSEC3 code to not break on broken auth zones that include unsigned out of zone (above apex) diff --git a/doc/example.conf.in b/doc/example.conf.in index db54f9739..c2bc6e9d8 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -117,7 +117,7 @@ server: # buffer size for UDP port 53 outgoing (SO_SNDBUF socket option). # 0 is system default. Use 4m to handle spikes on very busy servers. - # so-sndbuf: 0 + # so-sndbuf: 1m # use SO_REUSEPORT to distribute queries over threads. # at extreme load it could be better to turn it off to distribute even. diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index e963a3e3a..3407b3414 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -355,11 +355,14 @@ If not 0, then set the SO_SNDBUF socket option to get more buffer space on UDP port 53 outgoing queries. This for very busy servers handles spikes in answer traffic, otherwise 'send: resource temporarily unavailable' can get logged, the buffer overrun is also visible by netstat \-su. -Default is 0 (use system value). Specify the number of bytes to ask -for, try "4m" on a very busy server. The OS caps it at a maximum, on -linux Unbound needs root permission to bypass the limit, or the admin -can use sysctl net.core.wmem_max. On BSD, Solaris changes are similar -to so\-rcvbuf. +Default is 1M. If set to 0 it uses the system value. Specify the number +of bytes to ask for, try "4m" on a very busy server. It needs some space +to be able to deal with packets that wait for local address resolution, +from like ARP and NDP discovery, before they are sent out, hence +it is elevated above the system default by default. The OS caps it at +a maximum, on linux Unbound needs root permission to bypass the limit, +or the admin can use sysctl net.core.wmem_max. On BSD, Solaris changes +are similar to so\-rcvbuf. .TP .B so\-reuseport: \fI If yes, then open dedicated listening sockets for incoming queries for each diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index 26efadc15..a3b4d4941 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -473,7 +473,8 @@ create_udp_sock(int family, int socktype, struct sockaddr* addr, "Got %u. To fix: start with " "root permissions(linux) or sysctl " "bigger net.core.wmem_max(linux) or " - "kern.ipc.maxsockbuf(bsd) values.", + "kern.ipc.maxsockbuf(bsd) values. or " + "set so-sndbuf: 0 (use system value).", (unsigned)snd, (unsigned)got); } # ifdef SO_SNDBUFFORCE diff --git a/util/config_file.c b/util/config_file.c index f1a26674e..328b62df8 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -210,7 +210,7 @@ config_create(void) cfg->if_automatic = 0; cfg->if_automatic_ports = NULL; cfg->so_rcvbuf = 0; - cfg->so_sndbuf = 0; + cfg->so_sndbuf = 1024*1024; cfg->so_reuseport = REUSEPORT_DEFAULT; cfg->ip_transparent = 0; cfg->ip_freebind = 0; From 1ef7b4a24619f63d551a632673d52dd894e3b85a Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 13 May 2025 15:31:05 +0200 Subject: [PATCH 126/218] - Adjusted so-sndbuf default to 4m. --- doc/Changelog | 1 + doc/example.conf.in | 4 ++-- doc/unbound.conf.5.in | 4 ++-- util/config_file.c | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index b7bcabb6d..dac34fd69 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -7,6 +7,7 @@ - Change default for so-sndbuf to 1m, to mitigate a cross-layer issue where the UDP socket send buffers are exhausted waiting for ARP/NDP resolution. Thanks to Reflyable for the report. + - Adjusted so-sndbuf default to 4m. 12 May 2025: Yorgos - Merge #1280: Fix auth nsec3 code. Fixes NSEC3 code to not break on diff --git a/doc/example.conf.in b/doc/example.conf.in index c2bc6e9d8..a85b58de4 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -116,8 +116,8 @@ server: # so-rcvbuf: 0 # buffer size for UDP port 53 outgoing (SO_SNDBUF socket option). - # 0 is system default. Use 4m to handle spikes on very busy servers. - # so-sndbuf: 1m + # 0 is system default. Set larger to handle spikes on very busy servers. + # so-sndbuf: 4m # use SO_REUSEPORT to distribute queries over threads. # at extreme load it could be better to turn it off to distribute even. diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 3407b3414..c469c3c66 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -355,8 +355,8 @@ If not 0, then set the SO_SNDBUF socket option to get more buffer space on UDP port 53 outgoing queries. This for very busy servers handles spikes in answer traffic, otherwise 'send: resource temporarily unavailable' can get logged, the buffer overrun is also visible by netstat \-su. -Default is 1M. If set to 0 it uses the system value. Specify the number -of bytes to ask for, try "4m" on a very busy server. It needs some space +Default is 4m. If set to 0 it uses the system value. Specify the number +of bytes to ask for, try "8m" on a very busy server. It needs some space to be able to deal with packets that wait for local address resolution, from like ARP and NDP discovery, before they are sent out, hence it is elevated above the system default by default. The OS caps it at diff --git a/util/config_file.c b/util/config_file.c index 328b62df8..b1e767b3b 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -210,7 +210,7 @@ config_create(void) cfg->if_automatic = 0; cfg->if_automatic_ports = NULL; cfg->so_rcvbuf = 0; - cfg->so_sndbuf = 1024*1024; + cfg->so_sndbuf = 4*1024*1024; cfg->so_reuseport = REUSEPORT_DEFAULT; cfg->ip_transparent = 0; cfg->ip_freebind = 0; From 1634beb4a0f360b86c422e6b47592609c958f926 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 15 May 2025 14:34:18 +0200 Subject: [PATCH 127/218] - Fix config of slab values when there is no config file. --- daemon/unbound.c | 1 + doc/Changelog | 3 +++ winrc/win_svc.c | 1 + 3 files changed, 5 insertions(+) diff --git a/daemon/unbound.c b/daemon/unbound.c index 8e59bb25a..143f2d160 100644 --- a/daemon/unbound.c +++ b/daemon/unbound.c @@ -747,6 +747,7 @@ run_daemon(const char* cfgfile, int cmdline_verbose, int debug_mode, int need_pi "the commandline to see more errors, " "or unbound-checkconf", cfgfile); log_warn("Continuing with default config settings"); + config_auto_slab_values(cfg); } apply_settings(daemon, cfg, cmdline_verbose, debug_mode); if(!done_setup) diff --git a/doc/Changelog b/doc/Changelog index dac34fd69..264a5fdd5 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +15 May 2025: Wouter + - Fix config of slab values when there is no config file. + 13 May 2025: Yorgos - Fix #1284: NULL pointer deref in az_find_nsec_cover() (latent bug) by adding a log_assert() to safeguard future development. diff --git a/winrc/win_svc.c b/winrc/win_svc.c index 40e12f1cf..429b045dc 100644 --- a/winrc/win_svc.c +++ b/winrc/win_svc.c @@ -328,6 +328,7 @@ service_init(int r, struct daemon** d, struct config_file** c) return 0; } log_warn("could not open config file, using defaults"); + config_auto_slab_values(cfg); } if(!r) report_status(SERVICE_START_PENDING, NO_ERROR, 2600); From 32644937b07f802318c306b850e0801117400b8a Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Mon, 19 May 2025 13:17:21 +0200 Subject: [PATCH 128/218] - Fix for cname chain length with qtype ANY and qname minimisation. Thanks to Jim Greenwood from Nominet for the report. --- doc/Changelog | 4 + iterator/iterator.c | 22 +- testdata/iter_minimise_chain.rpl | 623 +++++++++++++++++++++++++++++++ 3 files changed, 644 insertions(+), 5 deletions(-) create mode 100644 testdata/iter_minimise_chain.rpl diff --git a/doc/Changelog b/doc/Changelog index 264a5fdd5..289372adc 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +19 May 2025: Wouter + - Fix for cname chain length with qtype ANY and qname minimisation. + Thanks to Jim Greenwood from Nominet for the report. + 15 May 2025: Wouter - Fix config of slab values when there is no config file. diff --git a/iterator/iterator.c b/iterator/iterator.c index e64dfa61b..320faf734 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -3247,13 +3247,19 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, } } if(type == RESPONSE_TYPE_CNAME && - iq->qchase.qtype == LDNS_RR_TYPE_CNAME && + (iq->qchase.qtype == LDNS_RR_TYPE_CNAME || + iq->qchase.qtype == LDNS_RR_TYPE_ANY) && iq->minimisation_state == MINIMISE_STATE && query_dname_compare(iq->qchase.qname, iq->qinfo_out.qname) == 0) { /* The minimised query for full QTYPE and hidden QTYPE can be * classified as CNAME response type, even when the original * QTYPE=CNAME. This should be treated as answer response type. */ + /* For QTYPE=ANY, it is also considered the response, that + * is what the classifier would say, if it saw qtype ANY, + * and this same response was returned for that. The response + * can already be treated as such an answer, without having + * to send another query with a new qtype. */ type = RESPONSE_TYPE_ANSWER; } @@ -3510,6 +3516,15 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, iq->num_target_queries = 0; return processDSNSFind(qstate, iq, id); } + if(iq->minimisation_state == MINIMISE_STATE && + query_dname_compare(iq->qchase.qname, + iq->qinfo_out.qname) != 0) { + verbose(VERB_ALGO, "continue query minimisation, " + "downwards, after CNAME response for " + "intermediate label"); + /* continue query minimisation, downwards */ + return next_state(iq, QUERYTARGETS_STATE); + } /* Process the CNAME response. */ if(!handle_cname_response(qstate, iq, iq->response, &sname, &snamelen)) { @@ -3572,10 +3587,7 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, iq->auth_zone_response = 0; iq->sent_count = 0; iq->dp_target_count = 0; - if(iq->minimisation_state != MINIMISE_STATE) - /* Only count as query restart when it is not an extra - * query as result of qname minimisation. */ - iq->query_restart_count++; + iq->query_restart_count++; if(qstate->env->cfg->qname_minimisation) iq->minimisation_state = INIT_MINIMISE_STATE; diff --git a/testdata/iter_minimise_chain.rpl b/testdata/iter_minimise_chain.rpl new file mode 100644 index 000000000..97fefaf95 --- /dev/null +++ b/testdata/iter_minimise_chain.rpl @@ -0,0 +1,623 @@ +; config options +server: + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: yes + max-query-restarts: 11 + max-global-quota: 120 + +stub-zone: + name: "." + stub-addr: 193.0.14.129 +CONFIG_END + +SCENARIO_BEGIN Test qname minimisation and long cname chain. + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 1000 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION AUTHORITY +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END +RANGE_END + +; a.gtld-servers.net. +RANGE_BEGIN 0 1000 + ADDRESS 192.5.6.30 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +com. IN A +SECTION AUTHORITY +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.example.com. +RANGE_BEGIN 0 1000 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +example.com. IN NS +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain1.example.com. IN CNAME +SECTION ANSWER +chain1.example.com. IN CNAME chain2.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain2.example.com. IN CNAME +SECTION ANSWER +chain2.example.com. IN CNAME chain3.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain3.example.com. IN CNAME +SECTION ANSWER +chain3.example.com. IN CNAME chain4.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain4.example.com. IN CNAME +SECTION ANSWER +chain4.example.com. IN CNAME chain5.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain5.example.com. IN CNAME +SECTION ANSWER +chain5.example.com. IN CNAME chain6.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain6.example.com. IN CNAME +SECTION ANSWER +chain6.example.com. IN CNAME chain7.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain7.example.com. IN CNAME +SECTION ANSWER +chain7.example.com. IN CNAME chain8.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain8.example.com. IN CNAME +SECTION ANSWER +chain8.example.com. IN CNAME chain9.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain9.example.com. IN CNAME +SECTION ANSWER +chain9.example.com. IN CNAME chain10.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain10.example.com. IN CNAME +SECTION ANSWER +chain10.example.com. IN CNAME chain11.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain11.example.com. IN CNAME +SECTION ANSWER +chain11.example.com. IN CNAME chain12.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain12.example.com. IN CNAME +SECTION ANSWER +chain12.example.com. IN CNAME chain13.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain13.example.com. IN CNAME +SECTION ANSWER +chain13.example.com. IN CNAME chain14.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain14.example.com. IN CNAME +SECTION ANSWER +chain14.example.com. IN CNAME chain15.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain15.example.com. IN CNAME +SECTION ANSWER +chain15.example.com. IN CNAME chain16.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain16.example.com. IN CNAME +SECTION ANSWER +chain16.example.com. IN CNAME chain17.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain17.example.com. IN CNAME +SECTION ANSWER +chain17.example.com. IN CNAME chain18.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain18.example.com. IN CNAME +SECTION ANSWER +chain18.example.com. IN CNAME chain19.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain19.example.com. IN CNAME +SECTION ANSWER +chain19.example.com. IN CNAME chain20.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain20.example.com. IN CNAME +SECTION ANSWER +chain20.example.com. IN CNAME chain21.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain21.example.com. IN CNAME +SECTION ANSWER +chain21.example.com. IN CNAME chain22.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain22.example.com. IN CNAME +SECTION ANSWER +chain22.example.com. IN CNAME chain23.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain23.example.com. IN CNAME +SECTION ANSWER +chain23.example.com. IN CNAME chain24.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain24.example.com. IN CNAME +SECTION ANSWER +chain24.example.com. IN CNAME chain25.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain25.example.com. IN CNAME +SECTION ANSWER +chain25.example.com. IN CNAME chain26.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain26.example.com. IN CNAME +SECTION ANSWER +chain26.example.com. IN CNAME chain27.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain27.example.com. IN CNAME +SECTION ANSWER +chain27.example.com. IN CNAME chain28.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain28.example.com. IN CNAME +SECTION ANSWER +chain28.example.com. IN CNAME chain29.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain29.example.com. IN CNAME +SECTION ANSWER +chain29.example.com. IN CNAME chain30.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain30.example.com. IN CNAME +SECTION ANSWER +chain30.example.com. IN CNAME chain31.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain31.example.com. IN CNAME +SECTION ANSWER +chain31.example.com. IN CNAME chain32.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain32.example.com. IN CNAME +SECTION ANSWER +chain32.example.com. IN CNAME chain33.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain33.example.com. IN CNAME +SECTION ANSWER +chain33.example.com. IN CNAME chain34.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain34.example.com. IN CNAME +SECTION ANSWER +chain34.example.com. IN CNAME chain35.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain35.example.com. IN CNAME +SECTION ANSWER +chain35.example.com. IN CNAME chain36.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain36.example.com. IN CNAME +SECTION ANSWER +chain36.example.com. IN CNAME chain37.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain37.example.com. IN CNAME +SECTION ANSWER +chain37.example.com. IN CNAME chain38.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain38.example.com. IN CNAME +SECTION ANSWER +chain38.example.com. IN CNAME chain39.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain39.example.com. IN CNAME +SECTION ANSWER +chain39.example.com. IN CNAME chain40.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +chain40.example.com. IN CNAME +SECTION ANSWER +chain40.example.com. IN CNAME chain41.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +sub1.chain1.example.com. IN A +SECTION ANSWER +sub1.chain1.example.com. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +sub1.chain1.example.com. IN ANY +SECTION ANSWER +sub1.chain1.example.com. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +sub2.chain2.example.com. IN A +SECTION ANSWER +sub2.chain2.example.com. IN CNAME sub2-2.chain2.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname +ADJUST copy_id copy_query +REPLY QR AA NOERROR +SECTION QUESTION +sub2-2.chain2.example.com. IN A +SECTION ANSWER +sub2-2.chain2.example.com. IN CNAME sub2-3.chain2.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +sub2-3.chain1.example.com. IN ANY +SECTION ANSWER +sub2-3.chain1.example.com. IN A 1.2.3.6 +ENTRY_END +RANGE_END + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +chain1.example.com. IN A +ENTRY_END + +STEP 20 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA SERVFAIL +SECTION QUESTION +chain1.example.com. IN A +SECTION ANSWER +ENTRY_END + +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +chain13.example.com. IN ANY +ENTRY_END + +STEP 40 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +chain13.example.com. IN ANY +SECTION ANSWER +chain13.example.com. IN CNAME chain14.example.com. +ENTRY_END + +STEP 49 TIME_PASSES ELAPSE 7200 ; expire the previous records. +STEP 50 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +chain1.example.com. IN ANY +ENTRY_END + +STEP 60 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +chain1.example.com. IN ANY +SECTION ANSWER +chain1.example.com. IN CNAME chain2.example.com. +ENTRY_END + +STEP 69 TIME_PASSES ELAPSE 7200 ; expire the previous records. +STEP 70 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +sub1.chain1.example.com. IN ANY +ENTRY_END + +STEP 80 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +sub1.chain1.example.com. IN ANY +SECTION ANSWER +sub1.chain1.example.com. IN A 1.2.3.5 +ENTRY_END + +STEP 90 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +sub2.chain2.example.com. IN ANY +ENTRY_END + +STEP 100 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +sub2.chain2.example.com. IN ANY +SECTION ANSWER +sub2.chain2.example.com. IN CNAME sub2-2.chain2.example.com. +ENTRY_END + +SCENARIO_END From 6662f71732fb2efba5eee5a2e93d1e8da1b2249f Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Tue, 20 May 2025 12:20:20 +0200 Subject: [PATCH 129/218] RST man pages (#1285) Introduce restructuredText man pages to sync the online and source code man page documentation. The templated man pages (*.in) are still part of the repo but generated with docutils from their .rst counterpart. Documentation on how to generate those (mainly for core developers) are in README.man. --- doc/README.man | 16 + doc/libunbound.3.in | 673 ++-- doc/libunbound.rst | 491 +++ doc/unbound-anchor.8.in | 379 +- doc/unbound-anchor.rst | 281 ++ doc/unbound-checkconf.8.in | 121 +- doc/unbound-checkconf.rst | 98 + doc/unbound-control.8.in | 1817 ++++++--- doc/unbound-control.rst | 1354 +++++++ doc/unbound-host.1.in | 242 +- doc/unbound-host.rst | 176 + doc/unbound.8.in | 169 +- doc/unbound.conf.5.in | 7545 ++++++++++++++++++++++++------------ doc/unbound.conf.rst | 4996 ++++++++++++++++++++++++ doc/unbound.rst | 119 + 15 files changed, 14625 insertions(+), 3852 deletions(-) create mode 100644 doc/README.man create mode 100644 doc/libunbound.rst create mode 100644 doc/unbound-anchor.rst create mode 100644 doc/unbound-checkconf.rst create mode 100644 doc/unbound-control.rst create mode 100644 doc/unbound-host.rst create mode 100644 doc/unbound.conf.rst create mode 100644 doc/unbound.rst diff --git a/doc/README.man b/doc/README.man new file mode 100644 index 000000000..8e7897f47 --- /dev/null +++ b/doc/README.man @@ -0,0 +1,16 @@ +After Unbound 1.23.0, the source of the man pages is in reStructuredText format. + +This helps with the online documentation at https://unbound.docs.nlnetlabs.nl +and makes it easier to maintain and contribute to the documentation. + +The templated man pages (*.in) are still part of the code repository as to not +alter current procedures that could be in place by users/packagers. + +The templated man pages (*.in) are generated by Sphinx (used for the online +documentation). +The online documentation has its own repository at +https://github.com/NLnetLabs/unbound-manual. + +In the README.md there (branch test-auto for now), there are further simple +instructions on how to generate the templated man pages there and update them +in this repository. diff --git a/doc/libunbound.3.in b/doc/libunbound.3.in index 548969142..204cda20c 100644 --- a/doc/libunbound.3.in +++ b/doc/libunbound.3.in @@ -1,335 +1,306 @@ -.TH "libunbound" "3" "@date@" "NLnet Labs" "unbound @version@" -.\" -.\" libunbound.3 -- unbound library functions manual -.\" -.\" Copyright (c) 2007, NLnet Labs. All rights reserved. -.\" -.\" See LICENSE for the license. -.\" -.\" -.SH "NAME" -.B libunbound, -.B unbound.h, -.B ub_ctx, -.B ub_result, -.B ub_callback_type, -.B ub_ctx_create, -.B ub_ctx_delete, -.B ub_ctx_set_option, -.B ub_ctx_get_option, -.B ub_ctx_config, -.B ub_ctx_set_fwd, -.B ub_ctx_set_stub, -.B ub_ctx_set_tls, -.B ub_ctx_resolvconf, -.B ub_ctx_hosts, -.B ub_ctx_add_ta, -.B ub_ctx_add_ta_autr, -.B ub_ctx_add_ta_file, -.B ub_ctx_trustedkeys, -.B ub_ctx_debugout, -.B ub_ctx_debuglevel, -.B ub_ctx_async, -.B ub_poll, -.B ub_wait, -.B ub_fd, -.B ub_process, -.B ub_resolve, -.B ub_resolve_async, -.B ub_cancel, -.B ub_resolve_free, -.B ub_strerror, -.B ub_ctx_print_local_zones, -.B ub_ctx_zone_add, -.B ub_ctx_zone_remove, -.B ub_ctx_data_add, -.B ub_ctx_data_remove -\- Unbound DNS validating resolver @version@ functions. -.SH "SYNOPSIS" -.B #include -.LP -\fIstruct ub_ctx *\fR -\fBub_ctx_create\fR(\fIvoid\fR); -.LP -\fIvoid\fR -\fBub_ctx_delete\fR(\fIstruct ub_ctx*\fR ctx); -.LP -\fIint\fR -\fBub_ctx_set_option\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR opt, \fIchar*\fR val); -.LP -\fIint\fR -\fBub_ctx_get_option\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR opt, \fIchar**\fR val); -.LP -\fIint\fR -\fBub_ctx_config\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR fname); -.LP -\fIint\fR -\fBub_ctx_set_fwd\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR addr); -.LP -\fIint\fR -\fBub_ctx_set_stub\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR zone, -\fIchar*\fR addr, -.br - \fIint\fR isprime); -.LP -\fIint\fR -\fBub_ctx_set_tls\fR(\fIstruct ub_ctx*\fR ctx, \fIint\fR tls); -.LP -\fIint\fR -\fBub_ctx_resolvconf\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR fname); -.LP -\fIint\fR -\fBub_ctx_hosts\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR fname); -.LP -\fIint\fR -\fBub_ctx_add_ta\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR ta); -.LP -\fIint\fR -\fBub_ctx_add_ta_autr\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR fname); -.LP -\fIint\fR -\fBub_ctx_add_ta_file\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR fname); -.LP -\fIint\fR -\fBub_ctx_trustedkeys\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR fname); -.LP -\fIint\fR -\fBub_ctx_debugout\fR(\fIstruct ub_ctx*\fR ctx, \fIFILE*\fR out); -.LP -\fIint\fR -\fBub_ctx_debuglevel\fR(\fIstruct ub_ctx*\fR ctx, \fIint\fR d); -.LP -\fIint\fR -\fBub_ctx_async\fR(\fIstruct ub_ctx*\fR ctx, \fIint\fR dothread); -.LP -\fIint\fR -\fBub_poll\fR(\fIstruct ub_ctx*\fR ctx); -.LP -\fIint\fR -\fBub_wait\fR(\fIstruct ub_ctx*\fR ctx); -.LP -\fIint\fR -\fBub_fd\fR(\fIstruct ub_ctx*\fR ctx); -.LP -\fIint\fR -\fBub_process\fR(\fIstruct ub_ctx*\fR ctx); -.LP -\fIint\fR -\fBub_resolve\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR name, -.br - \fIint\fR rrtype, \fIint\fR rrclass, \fIstruct ub_result**\fR result); -.LP -\fIint\fR -\fBub_resolve_async\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR name, -.br - \fIint\fR rrtype, \fIint\fR rrclass, \fIvoid*\fR mydata, -.br - \fIub_callback_type\fR callback, \fIint*\fR async_id); -.LP -\fIint\fR -\fBub_cancel\fR(\fIstruct ub_ctx*\fR ctx, \fIint\fR async_id); -.LP -\fIvoid\fR -\fBub_resolve_free\fR(\fIstruct ub_result*\fR result); -.LP -\fIconst char *\fR -\fBub_strerror\fR(\fIint\fR err); -.LP -\fIint\fR -\fBub_ctx_print_local_zones\fR(\fIstruct ub_ctx*\fR ctx); -.LP -\fIint\fR -\fBub_ctx_zone_add\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR zone_name, \fIchar*\fR zone_type); -.LP -\fIint\fR -\fBub_ctx_zone_remove\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR zone_name); -.LP -\fIint\fR -\fBub_ctx_data_add\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR data); -.LP -\fIint\fR -\fBub_ctx_data_remove\fR(\fIstruct ub_ctx*\fR ctx, \fIchar*\fR data); -.SH "DESCRIPTION" -.B Unbound -is an implementation of a DNS resolver, that does caching and -DNSSEC validation. This is the library API, for using the \-lunbound library. -The server daemon is described in \fIunbound\fR(8). -The library works independent from a running unbound server, and -can be used to convert hostnames to ip addresses, and back, -and obtain other information from the DNS. The library performs public\-key -validation of results with DNSSEC. -.P -The library uses a variable of type \fIstruct ub_ctx\fR to keep context -between calls. The user must maintain it, creating it with -.B ub_ctx_create -and deleting it with -.B ub_ctx_delete\fR. -It can be created and deleted at any time. Creating it anew removes any -previous configuration (such as trusted keys) and clears any cached results. -.P -The functions are thread\-safe, and a context can be used in a threaded (as -well as in a non\-threaded) environment. Also resolution (and validation) -can be performed blocking and non\-blocking (also called asynchronous). -The async method returns from the call immediately, so that processing -can go on, while the results become available later. -.P +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "LIBUNBOUND" "3" "@date@" "@version@" "Unbound" +.SH NAME +libunbound \- Unbound DNS validating resolver @version@ functions. +.SH SYNOPSIS +.sp +\fB#include \fP +.sp +struct ub_ctx * \fBub_ctx_create\fP(void); +.sp +void \fBub_ctx_delete\fP(struct ub_ctx* ctx); +.sp +int \fBub_ctx_set_option\fP(struct ub_ctx* ctx, char* opt, char* val); +.sp +int \fBub_ctx_get_option\fP(struct ub_ctx* ctx, char* opt, char** val); +.sp +int \fBub_ctx_config\fP(struct ub_ctx* ctx, char* fname); +.sp +int \fBub_ctx_set_fwd\fP(struct ub_ctx* ctx, char* addr); +.INDENT 0.0 +.TP +int \fBub_ctx_set_stub\fP(struct ub_ctx* ctx, char* zone, char* addr, +int isprime); +.UNINDENT +.sp +int \fBub_ctx_set_tls\fP(struct ub_ctx* ctx, int tls); +.sp +int \fBub_ctx_resolvconf\fP(struct ub_ctx* ctx, char* fname); +.sp +int \fBub_ctx_hosts\fP(struct ub_ctx* ctx, char* fname); +.sp +int \fBub_ctx_add_ta\fP(struct ub_ctx* ctx, char* ta); +.sp +int \fBub_ctx_add_ta_autr\fP(struct ub_ctx* ctx, char* fname); +.sp +int \fBub_ctx_add_ta_file\fP(struct ub_ctx* ctx, char* fname); +.sp +int \fBub_ctx_trustedkeys\fP(struct ub_ctx* ctx, char* fname); +.sp +int \fBub_ctx_debugout\fP(struct ub_ctx* ctx, FILE* out); +.sp +int \fBub_ctx_debuglevel\fP(struct ub_ctx* ctx, int d); +.sp +int \fBub_ctx_async\fP(struct ub_ctx* ctx, int dothread); +.sp +int \fBub_poll\fP(struct ub_ctx* ctx); +.sp +int \fBub_wait\fP(struct ub_ctx* ctx); +.sp +int \fBub_fd\fP(struct ub_ctx* ctx); +.sp +int \fBub_process\fP(struct ub_ctx* ctx); +.INDENT 0.0 +.TP +int \fBub_resolve\fP(struct ub_ctx* ctx, char* name, +int rrtype, int rrclass, struct ub_result** result); +.TP +int \fBub_resolve_async\fP(struct ub_ctx* ctx, char* name, +int rrtype, int rrclass, void* mydata, +ub_callback_type* callback, int* async_id); +.UNINDENT +.sp +int \fBub_cancel\fP(struct ub_ctx* ctx, int async_id); +.sp +void \fBub_resolve_free\fP(struct ub_result* result); +.sp +const char * \fBub_strerror\fP(int err); +.sp +int \fBub_ctx_print_local_zones\fP(struct ub_ctx* ctx); +.sp +int \fBub_ctx_zone_add\fP(struct ub_ctx* ctx, char* zone_name, char* zone_type); +.sp +int \fBub_ctx_zone_remove\fP(struct ub_ctx* ctx, char* zone_name); +.sp +int \fBub_ctx_data_add\fP(struct ub_ctx* ctx, char* data); +.sp +int \fBub_ctx_data_remove\fP(struct ub_ctx* ctx, char* data); +.SH DESCRIPTION +.sp +Unbound is an implementation of a DNS resolver, that does caching and DNSSEC +validation. +This is the library API, for using the \fB\-lunbound\fP library. +The server daemon is described in \fI\%unbound(8)\fP\&. +The library works independent from a running unbound server, and can be used to +convert hostnames to ip addresses, and back, and obtain other information from +the DNS. +The library performs public\-key validation of results with DNSSEC. +.sp +The library uses a variable of type \fIstruct ub_ctx\fP to keep context between +calls. +The user must maintain it, creating it with \fBub_ctx_create\fP and deleting it +with \fBub_ctx_delete\fP\&. +It can be created and deleted at any time. +Creating it anew removes any previous configuration (such as trusted keys) and +clears any cached results. +.sp +The functions are thread\-safe, and a context can be used in a threaded (as well +as in a non\-threaded) environment. +Also resolution (and validation) can be performed blocking and non\-blocking +(also called asynchronous). +The async method returns from the call immediately, so that processing can go +on, while the results become available later. +.sp The functions are discussed in turn below. -.SH "FUNCTIONS" -.TP +.SH FUNCTIONS +.INDENT 0.0 +.TP .B ub_ctx_create Create a new context, initialised with defaults. -The information from /etc/resolv.conf and /etc/hosts is not utilised -by default. Use -.B ub_ctx_resolvconf -and -.B ub_ctx_hosts -to read them. -Before you call this, use the openssl functions CRYPTO_set_id_callback and -CRYPTO_set_locking_callback to set up asynchronous operation if you use -lib openssl (the application calls these functions once for initialisation). -Openssl 1.0.0 or later uses the CRYPTO_THREADID_set_callback function. +The information from \fB/etc/resolv.conf\fP and \fB/etc/hosts\fP is +not utilised by default. +Use \fBub_ctx_resolvconf\fP and \fBub_ctx_hosts\fP to read them. +Before you call this, use the openssl functions +\fBCRYPTO_set_id_callback\fP and \fBCRYPTO_set_locking_callback\fP to set +up asynchronous operation if you use lib openssl (the application calls +these functions once for initialisation). +Openssl 1.0.0 or later uses the \fBCRYPTO_THREADID_set_callback\fP +function. .TP .B ub_ctx_delete Delete validation context and free associated resources. -Outstanding async queries are killed and callbacks are not called for them. +Outstanding async queries are killed and callbacks are not called for +them. .TP .B ub_ctx_set_option -A power\-user interface that lets you specify one of the options from the -config file format, see \fIunbound.conf\fR(5). Not all options are -relevant. For some specific options, such as adding trust anchors, special -routines exist. Pass the option name with the trailing ':'. +A power\-user interface that lets you specify one of the options from +the config file format, see \fI\%unbound.conf(5)\fP\&. +Not all options are relevant. +For some specific options, such as adding trust anchors, special +routines exist. +Pass the option name with the trailing \fB\(aq:\(aq\fP\&. .TP .B ub_ctx_get_option -A power\-user interface that gets an option value. Some options cannot be -gotten, and others return a newline separated list. Pass the option name -without trailing ':'. The returned value must be free(2)d by the caller. +A power\-user interface that gets an option value. +Some options cannot be gotten, and others return a newline separated +list. +Pass the option name without trailing \fB\(aq:\(aq\fP\&. +The returned value must be free(2)d by the caller. .TP .B ub_ctx_config -A power\-user interface that lets you specify an unbound config file, see -\fIunbound.conf\fR(5), which is read for configuration. Not all options are -relevant. For some specific options, such as adding trust anchors, special -routines exist. This function is thread\-safe only if a single instance of -ub_ctx* exists in the application. If several instances exist the -application has to ensure that ub_ctx_config is not called in parallel by -the different instances. +A power\-user interface that lets you specify an unbound config file, +see \fI\%unbound.conf(5)\fP, which is read for +configuration. +Not all options are relevant. +For some specific options, such as adding trust anchors, special +routines exist. +This function is thread\-safe only if a single instance of \fBub_ctx\fP* +exists in the application. +If several instances exist the application has to ensure that +\fBub_ctx_config\fP is not called in parallel by the different instances. .TP .B ub_ctx_set_fwd -Set machine to forward DNS queries to, the caching resolver to use. -IP4 or IP6 address. Forwards all DNS requests to that machine, which -is expected to run a recursive resolver. If the proxy is not -DNSSEC capable, validation may fail. Can be called several times, in -that case the addresses are used as backup servers. -At this time it is only possible to set configuration before the -first resolve is done. +Set machine to forward DNS queries to, the caching resolver to use. +IP4 or IP6 address. +Forwards all DNS requests to that machine, which is expected to run a +recursive resolver. +If the proxy is not DNSSEC capable, validation may fail. +Can be called several times, in that case the addresses are used as +backup servers. +At this time it is only possible to set configuration before the first +resolve is done. .TP .B ub_ctx_set_stub -Set a stub zone, authoritative dns servers to use for a particular zone. -IP4 or IP6 address. If the address is NULL the stub entry is removed. -Set isprime true if you configure root hints with it. Otherwise similar to -the stub zone item from unbound's config file. Can be called several times, -for different zones, or to add multiple addresses for a particular zone. -At this time it is only possible to set configuration before the -first resolve is done. +Set a stub zone, authoritative dns servers to use for a particular +zone. +IP4 or IP6 address. +If the address is NULL the stub entry is removed. +Set isprime true if you configure root hints with it. +Otherwise similar to the stub zone item from unbound\(aqs config file. +Can be called several times, for different zones, or to add multiple +addresses for a particular zone. +At this time it is only possible to set configuration before the first +resolve is done. .TP .B ub_ctx_set_tls -Enable DNS over TLS (DoT) for machines set with -.B ub_ctx_set_fwd. -At this time it is only possible to set configuration before the -first resolve is done. +Enable DNS over TLS (DoT) for machines set with \fBub_ctx_set_fwd\fP\&. +At this time it is only possible to set configuration before the first +resolve is done. .TP .B ub_ctx_resolvconf -By default the root servers are queried and full resolver mode is used, but -you can use this call to read the list of nameservers to use from the -filename given. -Usually "/etc/resolv.conf". Uses those nameservers as caching proxies. +By default the root servers are queried and full resolver mode is used, +but you can use this call to read the list of nameservers to use from +the filename given. +Usually \fB\(dq/etc/resolv.conf\(dq\fP\&. +Uses those nameservers as caching proxies. If they do not support DNSSEC, validation may fail. Only nameservers are picked up, the searchdomain, ndots and other -settings from \fIresolv.conf\fR(5) are ignored. -If fname NULL is passed, "/etc/resolv.conf" is used (if on Windows, -the system\-wide configured nameserver is picked instead). -At this time it is only possible to set configuration before the -first resolve is done. +settings from \fIresolv.conf(5)\fP are ignored. +If fname NULL is passed, \fB\(dq/etc/resolv.conf\(dq\fP is used (if on +Windows, the system\-wide configured nameserver is picked instead). +At this time it is only possible to set configuration before the first +resolve is done. .TP .B ub_ctx_hosts Read list of hosts from the filename given. -Usually "/etc/hosts". When queried for, these addresses are not marked -DNSSEC secure. If fname NULL is passed, "/etc/hosts" is used -(if on Windows, etc/hosts from WINDIR is picked instead). -At this time it is only possible to set configuration before the -first resolve is done. +Usually \fB\(dq/etc/hosts\(dq\fP\&. +When queried for, these addresses are not marked DNSSEC secure. +If fname NULL is passed, \fB\(dq/etc/hosts\(dq\fP is used (if on Windows, +\fBetc/hosts\fP from WINDIR is picked instead). +At this time it is only possible to set configuration before the first +resolve is done. .TP -.B -ub_ctx_add_ta +.B ub_ctx_add_ta Add a trust anchor to the given context. -At this time it is only possible to add trusted keys before the -first resolve is done. +At this time it is only possible to add trusted keys before the first +resolve is done. The format is a string, similar to the zone\-file format, -[domainname] [type] [rdata contents]. Both DS and DNSKEY records are accepted. +\fB[domainname]\fP \fB[type]\fP \fB[rdata contents]\fP\&. +Both DS and DNSKEY records are accepted. .TP .B ub_ctx_add_ta_autr -Add filename with automatically tracked trust anchor to the given context. -Pass name of a file with the managed trust anchor. You can create this -file with \fIunbound\-anchor\fR(8) for the root anchor. You can also -create it with an initial file with one line with a DNSKEY or DS record. +Add filename with automatically tracked trust anchor to the given +context. +Pass name of a file with the managed trust anchor. +You can create this file with +\fI\%unbound\-anchor(8)\fP for the root anchor. +You can also create it with an initial file with one line with a DNSKEY +or DS record. If the file is writable, it is updated when the trust anchor changes. -At this time it is only possible to add trusted keys before the -first resolve is done. +At this time it is only possible to add trusted keys before the first +resolve is done. .TP .B ub_ctx_add_ta_file Add trust anchors to the given context. Pass name of a file with DS and DNSKEY records in zone file format. -At this time it is only possible to add trusted keys before the -first resolve is done. +At this time it is only possible to add trusted keys before the first +resolve is done. .TP .B ub_ctx_trustedkeys Add trust anchors to the given context. -Pass the name of a bind\-style config file with trusted\-keys{}. -At this time it is only possible to add trusted keys before the -first resolve is done. +Pass the name of a bind\-style config file with \fBtrusted\-keys{}\fP\&. +At this time it is only possible to add trusted keys before the first +resolve is done. .TP .B ub_ctx_debugout -Set debug and error log output to the given stream. Pass NULL to disable -output. Default is stderr. File\-names or using syslog can be enabled -using config options, this routine is for using your own stream. +Set debug and error log output to the given stream. +Pass NULL to disable output. +Default is stderr. +File\-names or using syslog can be enabled using config options, this +routine is for using your own stream. .TP .B ub_ctx_debuglevel -Set debug verbosity for the context. Output is directed to stderr. +Set debug verbosity for the context. +Output is directed to stderr. Higher debug level gives more output. .TP .B ub_ctx_async Set a context behaviour for asynchronous action. -if set to true, enables threading and a call to -.B ub_resolve_async +if set to true, enables threading and a call to \fBub_resolve_async\fP creates a thread to handle work in the background. If false, a process is forked to handle work in the background. -Changes to this setting after -.B ub_resolve_async -calls have been made have no effect (delete and re\-create the context -to change). +Changes to this setting after \fBub_resolve_async\fP calls have been made +have no effect (delete and re\-create the context to change). .TP .B ub_poll Poll a context to see if it has any new results. -Do not poll in a loop, instead extract the fd below to poll for readiness, -and then check, or wait using the wait routine. +Do not poll in a loop, instead extract the \fBfd\fP below to poll for +readiness, and then check, or wait using the wait routine. Returns 0 if nothing to read, or nonzero if a result is available. -If nonzero, call -.B ub_process -to do callbacks. +If nonzero, call \fBub_process\fP to do callbacks. .TP .B ub_wait -Wait for a context to finish with results. Calls -.B ub_process -after the wait for you. After the wait, there are no more outstanding -asynchronous queries. +Wait for a context to finish with results. +Calls \fBub_process\fP after the wait for you. +After the wait, there are no more outstanding asynchronous queries. .TP .B ub_fd -Get file descriptor. Wait for it to become readable, at this point -answers are returned from the asynchronous validating resolver. -Then call the \fBub_process\fR to continue processing. +Get file descriptor. +Wait for it to become readable, at this point answers are returned from +the asynchronous validating resolver. +Then call the \fBub_process\fP to continue processing. .TP .B ub_process Call this routine to continue processing results from the validating -resolver (when the fd becomes readable). +resolver (when the \fBfd\fP becomes readable). Will perform necessary callbacks. .TP .B ub_resolve @@ -340,95 +311,111 @@ The result structure is newly allocated with the resulting data. .TP .B ub_resolve_async Perform asynchronous resolution and validation of the target name. -Arguments mean the same as for \fBub_resolve\fR except no -data is returned immediately, instead a callback is called later. -The callback receives a copy of the mydata pointer, that you can use to pass -information to the callback. The callback type is a function pointer to -a function declared as -.IP -void my_callback_function(void* my_arg, int err, -.br - struct ub_result* result); -.IP -The async_id is returned so you can (at your option) decide to track it -and cancel the request if needed. If you pass a NULL pointer the async_id -is not returned. +Arguments mean the same as for \fBub_resolve\fP except no data is +returned immediately, instead a callback is called later. +The callback receives a copy of the mydata pointer, that you can use to +pass information to the callback. +The callback type is a function pointer to a function declared as: +.INDENT 7.0 +.INDENT 3.5 +.sp +.nf +.ft C +void my_callback_function(void* my_arg, int err, + struct ub_result* result); +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +The \fBasync_id\fP is returned so you can (at your option) decide to +track it and cancel the request if needed. +If you pass a NULL pointer the \fBasync_id\fP is not returned. .TP .B ub_cancel -Cancel an async query in progress. This may return an error if the query -does not exist, or the query is already being delivered, in that case you -may still get a callback for the query. +Cancel an async query in progress. +This may return an error if the query does not exist, or the query is +already being delivered, in that case you may still get a callback for +the query. .TP .B ub_resolve_free -Free struct ub_result contents after use. +Free struct \fBub_result\fP contents after use. .TP .B ub_strerror -Convert error value from one of the unbound library functions -to a human readable string. +Convert error value from one of the unbound library functions to a +human readable string. .TP .B ub_ctx_print_local_zones Debug printout the local authority information to debug output. .TP .B ub_ctx_zone_add -Add new zone to local authority info, like local\-zone \fIunbound.conf\fR(5) -statement. +Add new zone to local authority info, like local\-zone +\fI\%unbound.conf(5)\fP statement. .TP .B ub_ctx_zone_remove Delete zone from local authority info. .TP .B ub_ctx_data_add Add resource record data to local authority info, like local\-data -\fIunbound.conf\fR(5) statement. +\fI\%unbound.conf(5)\fP statement. .TP .B ub_ctx_data_remove Delete local authority data from the name given. -.SH "RESULT DATA STRUCTURE" -The result of the DNS resolution and validation is returned as -\fIstruct ub_result\fR. The result structure contains the following entries. -.P +.UNINDENT +.SH RESULT DATA STRUCTURE +.sp +The result of the DNS resolution and validation is returned as \fIstruct +ub_result\fP\&. +The result structure contains the following entries: +.INDENT 0.0 +.INDENT 3.5 +.sp .nf - struct ub_result { - char* qname; /* text string, original question */ - int qtype; /* type code asked for */ - int qclass; /* class code asked for */ - char** data; /* array of rdata items, NULL terminated*/ - int* len; /* array with lengths of rdata items */ - char* canonname; /* canonical name of result */ - int rcode; /* additional error code in case of no data */ - void* answer_packet; /* full network format answer packet */ - int answer_len; /* length of packet in octets */ - int havedata; /* true if there is data */ - int nxdomain; /* true if nodata because name does not exist */ - int secure; /* true if result is secure */ - int bogus; /* true if a security failure happened */ - char* why_bogus; /* string with error if bogus */ - int was_ratelimited; /* true if the query was ratelimited (SERVFAIL) by unbound */ - int ttl; /* number of seconds the result is valid */ - }; +.ft C +struct ub_result { + char* qname; /* text string, original question */ + int qtype; /* type code asked for */ + int qclass; /* class code asked for */ + char** data; /* array of rdata items, NULL terminated*/ + int* len; /* array with lengths of rdata items */ + char* canonname; /* canonical name of result */ + int rcode; /* additional error code in case of no data */ + void* answer_packet; /* full network format answer packet */ + int answer_len; /* length of packet in octets */ + int havedata; /* true if there is data */ + int nxdomain; /* true if nodata because name does not exist */ + int secure; /* true if result is secure */ + int bogus; /* true if a security failure happened */ + char* why_bogus; /* string with error if bogus */ + int was_ratelimited; /* true if the query was ratelimited (SERVFAIL) by unbound */ + int ttl; /* number of seconds the result is valid */ +}; +.ft P .fi -.P -If both secure and bogus are false, security was not enabled for the -domain of the query. Else, they are not both true, one of them is true. -.SH "RETURN VALUES" -Many routines return an error code. The value 0 (zero) denotes no error -happened. Other values can be passed to -.B ub_strerror -to obtain a readable error string. -.B ub_strerror -returns a zero terminated string. -.B ub_ctx_create -returns NULL on an error (a malloc failure). -.B ub_poll -returns true if some information may be available, false otherwise. -.B ub_fd -returns a file descriptor or \-1 on error. -.B ub_ctx_config -and -.B ub_ctx_resolvconf -attempt to leave errno informative on a function return with file read failure. -.SH "SEE ALSO" -\fIunbound.conf\fR(5), -\fIunbound\fR(8). -.SH "AUTHORS" -.B Unbound -developers are mentioned in the CREDITS file in the distribution. +.UNINDENT +.UNINDENT +.sp +If both secure and bogus are false, security was not enabled for the domain of +the query. +Else, they are not both true, one of them is true. +.SH RETURN VALUES +.sp +Many routines return an error code. +The value 0 (zero) denotes no error happened. +Other values can be passed to \fBub_strerror\fP to obtain a readable error +string. +\fBub_strerror\fP returns a zero terminated string. +\fBub_ctx_create\fP returns NULL on an error (a malloc failure). +\fBub_poll\fP returns true if some information may be available, false otherwise. +\fBub_fd\fP returns a file descriptor or \-1 on error. +\fBub_ctx_config\fP and \fBub_ctx_resolvconf\fP attempt to leave errno informative +on a function return with file read failure. +.SH SEE ALSO +.sp +\fI\%unbound.conf(5)\fP, \fI\%unbound(8)\fP\&. +.SH AUTHOR +Unbound developers are mentioned in the CREDITS file in the distribution. +.SH COPYRIGHT +1999-2025, NLnet Labs +.\" Generated by docutils manpage writer. +. diff --git a/doc/libunbound.rst b/doc/libunbound.rst new file mode 100644 index 000000000..97883dc55 --- /dev/null +++ b/doc/libunbound.rst @@ -0,0 +1,491 @@ +.. + WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE + +.. + IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE + +.. + IT HELPS PEOPLE DIFFING THE CHANGES + +.. + WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE + +.. + IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE + +.. + IT HELPS PEOPLE DIFFING THE CHANGES + +.. + WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE + +.. + IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE + +.. + IT HELPS PEOPLE DIFFING THE CHANGES + +.. + WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE + +.. + IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE + +.. + IT HELPS PEOPLE DIFFING THE CHANGES + +libunbound(3) +============= + +Synopsis +-------- + +.. only:: html + + .. code-block:: c + + #include + + struct ub_ctx * ub_ctx_create(void); + + void ub_ctx_delete(struct ub_ctx* ctx); + + int ub_ctx_set_option(struct ub_ctx* ctx, char* opt, char* val); + + int ub_ctx_get_option(struct ub_ctx* ctx, char* opt, char** val); + + int ub_ctx_config(struct ub_ctx* ctx, char* fname); + + int ub_ctx_set_fwd(struct ub_ctx* ctx, char* addr); + + int ub_ctx_set_stub(struct ub_ctx* ctx, char* zone, char* addr, + int isprime); + + int ub_ctx_set_tls(struct ub_ctx* ctx, int tls); + + int ub_ctx_resolvconf(struct ub_ctx* ctx, char* fname); + + int ub_ctx_hosts(struct ub_ctx* ctx, char* fname); + + int ub_ctx_add_ta(struct ub_ctx* ctx, char* ta); + + int ub_ctx_add_ta_autr(struct ub_ctx* ctx, char* fname); + + int ub_ctx_add_ta_file(struct ub_ctx* ctx, char* fname); + + int ub_ctx_trustedkeys(struct ub_ctx* ctx, char* fname); + + int ub_ctx_debugout(struct ub_ctx* ctx, FILE* out); + + int ub_ctx_debuglevel(struct ub_ctx* ctx, int d); + + int ub_ctx_async(struct ub_ctx* ctx, int dothread); + + int ub_poll(struct ub_ctx* ctx); + + int ub_wait(struct ub_ctx* ctx); + + int ub_fd(struct ub_ctx* ctx); + + int ub_process(struct ub_ctx* ctx); + + int ub_resolve(struct ub_ctx* ctx, char* name, int rrtype, + int rrclass, struct ub_result** result); + + int ub_resolve_async(struct ub_ctx* ctx, char* name, int rrtype, + int rrclass, void* mydata, ub_callback_type callback, + int* async_id); + + int ub_cancel(struct ub_ctx* ctx, int async_id); + + void ub_resolve_free(struct ub_result* result); + + const char * ub_strerror(int err); + + int ub_ctx_print_local_zones(struct ub_ctx* ctx); + + int ub_ctx_zone_add(struct ub_ctx* ctx, char* zone_name, char* zone_type); + + int ub_ctx_zone_remove(struct ub_ctx* ctx, char* zone_name); + + int ub_ctx_data_add(struct ub_ctx* ctx, char* data); + + int ub_ctx_data_remove(struct ub_ctx* ctx, char* data); + +.. only:: man + + **#include ** + + struct ub_ctx \* **ub_ctx_create**\ (void); + + void **ub_ctx_delete**\ (struct ub_ctx\* ctx); + + int **ub_ctx_set_option**\ (struct ub_ctx\* ctx, char\* opt, char\* val); + + int **ub_ctx_get_option**\ (struct ub_ctx\* ctx, char\* opt, char\*\* val); + + int **ub_ctx_config**\ (struct ub_ctx\* ctx, char* fname); + + int **ub_ctx_set_fwd**\ (struct ub_ctx\* ctx, char\* addr); + + int **ub_ctx_set_stub**\ (struct ub_ctx\* ctx, char\* zone, char\* addr, + int isprime); + + int **ub_ctx_set_tls**\ (struct ub_ctx\* ctx, int tls); + + int **ub_ctx_resolvconf**\ (struct ub_ctx\* ctx, char\* fname); + + int **ub_ctx_hosts**\ (struct ub_ctx\* ctx, char\* fname); + + int **ub_ctx_add_ta**\ (struct ub_ctx\* ctx, char\* ta); + + int **ub_ctx_add_ta_autr**\ (struct ub_ctx\* ctx, char\* fname); + + int **ub_ctx_add_ta_file**\ (struct ub_ctx\* ctx, char\* fname); + + int **ub_ctx_trustedkeys**\ (struct ub_ctx\* ctx, char\* fname); + + int **ub_ctx_debugout**\ (struct ub_ctx\* ctx, FILE\* out); + + int **ub_ctx_debuglevel**\ (struct ub_ctx\* ctx, int d); + + int **ub_ctx_async**\ (struct ub_ctx\* ctx, int dothread); + + int **ub_poll**\ (struct ub_ctx\* ctx); + + int **ub_wait**\ (struct ub_ctx\* ctx); + + int **ub_fd**\ (struct ub_ctx\* ctx); + + int **ub_process**\ (struct ub_ctx\* ctx); + + int **ub_resolve**\ (struct ub_ctx\* ctx, char\* name, + int rrtype, int rrclass, struct ub_result\*\* result); + + int **ub_resolve_async**\ (struct ub_ctx\* ctx, char\* name, + int rrtype, int rrclass, void\* mydata, + ub_callback_type\* callback, int\* async_id); + + int **ub_cancel**\ (struct ub_ctx\* ctx, int async_id); + + void **ub_resolve_free**\ (struct ub_result\* result); + + const char \* **ub_strerror**\ (int err); + + int **ub_ctx_print_local_zones**\ (struct ub_ctx\* ctx); + + int **ub_ctx_zone_add**\ (struct ub_ctx\* ctx, char\* zone_name, char\* zone_type); + + int **ub_ctx_zone_remove**\ (struct ub_ctx\* ctx, char\* zone_name); + + int **ub_ctx_data_add**\ (struct ub_ctx\* ctx, char\* data); + + int **ub_ctx_data_remove**\ (struct ub_ctx\* ctx, char\* data); + +Description +----------- + +Unbound is an implementation of a DNS resolver, that does caching and DNSSEC +validation. +This is the library API, for using the ``-lunbound`` library. +The server daemon is described in :doc:`unbound(8)`. +The library works independent from a running unbound server, and can be used to +convert hostnames to ip addresses, and back, and obtain other information from +the DNS. +The library performs public-key validation of results with DNSSEC. + +The library uses a variable of type *struct ub_ctx* to keep context between +calls. +The user must maintain it, creating it with **ub_ctx_create** and deleting it +with **ub_ctx_delete**. +It can be created and deleted at any time. +Creating it anew removes any previous configuration (such as trusted keys) and +clears any cached results. + +The functions are thread-safe, and a context can be used in a threaded (as well +as in a non-threaded) environment. +Also resolution (and validation) can be performed blocking and non-blocking +(also called asynchronous). +The async method returns from the call immediately, so that processing can go +on, while the results become available later. + +The functions are discussed in turn below. + +Functions +--------- + +.. glossary:: + + ub_ctx_create + Create a new context, initialised with defaults. + The information from :file:`/etc/resolv.conf` and :file:`/etc/hosts` is + not utilised by default. + Use **ub_ctx_resolvconf** and **ub_ctx_hosts** to read them. + Before you call this, use the openssl functions + **CRYPTO_set_id_callback** and **CRYPTO_set_locking_callback** to set + up asynchronous operation if you use lib openssl (the application calls + these functions once for initialisation). + Openssl 1.0.0 or later uses the **CRYPTO_THREADID_set_callback** + function. + + ub_ctx_delete + Delete validation context and free associated resources. + Outstanding async queries are killed and callbacks are not called for + them. + + ub_ctx_set_option + A power-user interface that lets you specify one of the options from + the config file format, see :doc:`unbound.conf(5)`. + Not all options are relevant. + For some specific options, such as adding trust anchors, special + routines exist. + Pass the option name with the trailing ``':'``. + + ub_ctx_get_option + A power-user interface that gets an option value. + Some options cannot be gotten, and others return a newline separated + list. + Pass the option name without trailing ``':'``. + The returned value must be free(2)d by the caller. + + ub_ctx_config + A power-user interface that lets you specify an unbound config file, + see :doc:`unbound.conf(5)`, which is read for + configuration. + Not all options are relevant. + For some specific options, such as adding trust anchors, special + routines exist. + This function is thread-safe only if a single instance of **ub_ctx**\* + exists in the application. + If several instances exist the application has to ensure that + **ub_ctx_config** is not called in parallel by the different instances. + + ub_ctx_set_fwd + Set machine to forward DNS queries to, the caching resolver to use. + IP4 or IP6 address. + Forwards all DNS requests to that machine, which is expected to run a + recursive resolver. + If the proxy is not DNSSEC capable, validation may fail. + Can be called several times, in that case the addresses are used as + backup servers. + At this time it is only possible to set configuration before the first + resolve is done. + + ub_ctx_set_stub + Set a stub zone, authoritative dns servers to use for a particular + zone. + IP4 or IP6 address. + If the address is NULL the stub entry is removed. + Set isprime true if you configure root hints with it. + Otherwise similar to the stub zone item from unbound's config file. + Can be called several times, for different zones, or to add multiple + addresses for a particular zone. + At this time it is only possible to set configuration before the first + resolve is done. + + ub_ctx_set_tls + Enable DNS over TLS (DoT) for machines set with **ub_ctx_set_fwd**. + At this time it is only possible to set configuration before the first + resolve is done. + + ub_ctx_resolvconf + By default the root servers are queried and full resolver mode is used, + but you can use this call to read the list of nameservers to use from + the filename given. + Usually :file:`"/etc/resolv.conf"`. + Uses those nameservers as caching proxies. + If they do not support DNSSEC, validation may fail. + Only nameservers are picked up, the searchdomain, ndots and other + settings from *resolv.conf(5)* are ignored. + If fname NULL is passed, :file:`"/etc/resolv.conf"` is used (if on + Windows, the system-wide configured nameserver is picked instead). + At this time it is only possible to set configuration before the first + resolve is done. + + ub_ctx_hosts + Read list of hosts from the filename given. + Usually :file:`"/etc/hosts"`. + When queried for, these addresses are not marked DNSSEC secure. + If fname NULL is passed, :file:`"/etc/hosts"` is used (if on Windows, + :file:`etc/hosts` from WINDIR is picked instead). + At this time it is only possible to set configuration before the first + resolve is done. + + ub_ctx_add_ta + Add a trust anchor to the given context. + At this time it is only possible to add trusted keys before the first + resolve is done. + The format is a string, similar to the zone-file format, + **[domainname]** **[type]** **[rdata contents]**. + Both DS and DNSKEY records are accepted. + + ub_ctx_add_ta_autr + Add filename with automatically tracked trust anchor to the given + context. + Pass name of a file with the managed trust anchor. + You can create this file with + :doc:`unbound-anchor(8)` for the root anchor. + You can also create it with an initial file with one line with a DNSKEY + or DS record. + If the file is writable, it is updated when the trust anchor changes. + At this time it is only possible to add trusted keys before the first + resolve is done. + + ub_ctx_add_ta_file + Add trust anchors to the given context. + Pass name of a file with DS and DNSKEY records in zone file format. + At this time it is only possible to add trusted keys before the first + resolve is done. + + ub_ctx_trustedkeys + Add trust anchors to the given context. + Pass the name of a bind-style config file with ``trusted-keys{}``. + At this time it is only possible to add trusted keys before the first + resolve is done. + + ub_ctx_debugout + Set debug and error log output to the given stream. + Pass NULL to disable output. + Default is stderr. + File-names or using syslog can be enabled using config options, this + routine is for using your own stream. + + ub_ctx_debuglevel + Set debug verbosity for the context. + Output is directed to stderr. + Higher debug level gives more output. + + ub_ctx_async + Set a context behaviour for asynchronous action. + if set to true, enables threading and a call to **ub_resolve_async** + creates a thread to handle work in the background. + If false, a process is forked to handle work in the background. + Changes to this setting after **ub_resolve_async** calls have been made + have no effect (delete and re-create the context to change). + + ub_poll + Poll a context to see if it has any new results. + Do not poll in a loop, instead extract the **fd** below to poll for + readiness, and then check, or wait using the wait routine. + Returns 0 if nothing to read, or nonzero if a result is available. + If nonzero, call **ub_process** to do callbacks. + + ub_wait + Wait for a context to finish with results. + Calls **ub_process** after the wait for you. + After the wait, there are no more outstanding asynchronous queries. + + ub_fd + Get file descriptor. + Wait for it to become readable, at this point answers are returned from + the asynchronous validating resolver. + Then call the **ub_process** to continue processing. + + ub_process + Call this routine to continue processing results from the validating + resolver (when the **fd** becomes readable). + Will perform necessary callbacks. + + ub_resolve + Perform resolution and validation of the target name. + The name is a domain name in a zero terminated text string. + The rrtype and rrclass are DNS type and class codes. + The result structure is newly allocated with the resulting data. + + ub_resolve_async + Perform asynchronous resolution and validation of the target name. + Arguments mean the same as for **ub_resolve** except no data is + returned immediately, instead a callback is called later. + The callback receives a copy of the mydata pointer, that you can use to + pass information to the callback. + The callback type is a function pointer to a function declared as: + + .. code-block:: c + + void my_callback_function(void* my_arg, int err, + struct ub_result* result); + + The **async_id** is returned so you can (at your option) decide to + track it and cancel the request if needed. + If you pass a NULL pointer the **async_id** is not returned. + + ub_cancel + Cancel an async query in progress. + This may return an error if the query does not exist, or the query is + already being delivered, in that case you may still get a callback for + the query. + + ub_resolve_free + Free struct **ub_result** contents after use. + + ub_strerror + Convert error value from one of the unbound library functions to a + human readable string. + + ub_ctx_print_local_zones + Debug printout the local authority information to debug output. + + ub_ctx_zone_add + Add new zone to local authority info, like local-zone + :doc:`unbound.conf(5)` statement. + + ub_ctx_zone_remove + Delete zone from local authority info. + + ub_ctx_data_add + Add resource record data to local authority info, like local-data + :doc:`unbound.conf(5)` statement. + + ub_ctx_data_remove + Delete local authority data from the name given. + +Result Data structure +--------------------- + +The result of the DNS resolution and validation is returned as *struct +ub_result*. +The result structure contains the following entries: + +.. code-block:: c + + struct ub_result { + char* qname; /* text string, original question */ + int qtype; /* type code asked for */ + int qclass; /* class code asked for */ + char** data; /* array of rdata items, NULL terminated*/ + int* len; /* array with lengths of rdata items */ + char* canonname; /* canonical name of result */ + int rcode; /* additional error code in case of no data */ + void* answer_packet; /* full network format answer packet */ + int answer_len; /* length of packet in octets */ + int havedata; /* true if there is data */ + int nxdomain; /* true if nodata because name does not exist */ + int secure; /* true if result is secure */ + int bogus; /* true if a security failure happened */ + char* why_bogus; /* string with error if bogus */ + int was_ratelimited; /* true if the query was ratelimited (SERVFAIL) by unbound */ + int ttl; /* number of seconds the result is valid */ + }; + +If both secure and bogus are false, security was not enabled for the domain of +the query. +Else, they are not both true, one of them is true. + +Return Values +------------- + +Many routines return an error code. +The value 0 (zero) denotes no error happened. +Other values can be passed to **ub_strerror** to obtain a readable error +string. +**ub_strerror** returns a zero terminated string. +**ub_ctx_create** returns NULL on an error (a malloc failure). +**ub_poll** returns true if some information may be available, false otherwise. +**ub_fd** returns a file descriptor or -1 on error. +**ub_ctx_config** and **ub_ctx_resolvconf** attempt to leave errno informative +on a function return with file read failure. + +See Also +-------- + +:doc:`unbound.conf(5)`, :doc:`unbound(8)`. diff --git a/doc/unbound-anchor.8.in b/doc/unbound-anchor.8.in index 31c4e9113..9c77b4cf7 100644 --- a/doc/unbound-anchor.8.in +++ b/doc/unbound-anchor.8.in @@ -1,189 +1,300 @@ -.TH "unbound-anchor" "8" "@date@" "NLnet Labs" "unbound @version@" -.\" -.\" unbound-anchor.8 -- unbound anchor maintenance utility manual -.\" -.\" Copyright (c) 2008, NLnet Labs. All rights reserved. -.\" -.\" See LICENSE for the license. -.\" -.\" -.SH "NAME" -.B unbound\-anchor -\- Unbound anchor utility. -.SH "SYNOPSIS" -.B unbound\-anchor -.RB [ opts ] -.SH "DESCRIPTION" -.B Unbound\-anchor -performs setup or update of the root trust anchor for DNSSEC validation. -The program fetches the trust anchor with the method from RFC7958 when -regular RFC5011 update fails to bring it up to date. +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "UNBOUND-ANCHOR" "8" "@date@" "@version@" "Unbound" +.SH NAME +unbound-anchor \- Unbound @version@ anchor utility. +.SH SYNOPSIS +.sp +\fBunbound\-anchor\fP [\fBopts\fP] +.SH DESCRIPTION +.sp +\fBunbound\-anchor\fP performs setup or update of the root trust anchor for DNSSEC +validation. +The program fetches the trust anchor with the method from \fI\%RFC 7958\fP when +regular \fI\%RFC 5011\fP update fails to bring it up to date. It can be run (as root) from the commandline, or run as part of startup -scripts. Before you start the \fIunbound\fR(8) DNS server. -.P +scripts. +Before you start the \fI\%unbound(8)\fP DNS server. +.sp Suggested usage: -.P +.INDENT 0.0 +.INDENT 3.5 +.sp .nf - # in the init scripts. - # provide or update the root anchor (if necessary) - unbound-anchor \-a "@UNBOUND_ROOTKEY_FILE@" - # Please note usage of this root anchor is at your own risk - # and under the terms of our LICENSE (see source). - # - # start validating resolver - # the unbound.conf contains: - # auto-trust-anchor-file: "@UNBOUND_ROOTKEY_FILE@" - unbound \-c unbound.conf +.ft C +# in the init scripts. +# provide or update the root anchor (if necessary) +unbound\-anchor \-a \(dq@UNBOUND_ROOTKEY_FILE@\(dq +# Please note usage of this root anchor is at your own risk +# and under the terms of our LICENSE (see source). +# +# start validating resolver +# the unbound.conf contains: +# auto\-trust\-anchor\-file: \(dq@UNBOUND_ROOTKEY_FILE@\(dq +unbound \-c unbound.conf +.ft P .fi -.P -This tool provides builtin default contents for the root anchor and root -update certificate files. -.P +.UNINDENT +.UNINDENT +.sp +This tool provides builtin default contents for the root anchor and root update +certificate files. +.sp It tests if the root anchor file works, and if not, and an update is possible, attempts to update the root anchor using the root update certificate. -It performs a https fetch of root-anchors.xml and checks the results (RFC7958), -if all checks are successful, it updates the root anchor file. Otherwise -the root anchor file is unchanged. It performs RFC5011 tracking if the -DNSSEC information available via the DNS makes that possible. -.P -It does not perform an update if the certificate is expired, if the network -is down or other errors occur. -.P +It performs a https fetch of +\fI\%root\-anchors.xml\fP +and checks the results (\fI\%RFC 7958\fP); if all checks are successful, it updates +the root anchor file. +Otherwise the root anchor file is unchanged. +It performs \fI\%RFC 5011\fP tracking if the DNSSEC information available via the +DNS makes that possible. +.sp +It does not perform an update if the certificate is expired, if the network is +down or other errors occur. +.sp The available options are: +.INDENT 0.0 .TP -.B \-a \fIfile +.B \-a The root anchor key file, that is read in and written out. -Default is @UNBOUND_ROOTKEY_FILE@. -If the file does not exist, or is empty, a builtin root key is written to it. +Default is \fB@UNBOUND_ROOTKEY_FILE@\fP\&. +If the file does not exist, or is empty, a builtin root key is written +to it. +.UNINDENT +.INDENT 0.0 .TP -.B \-c \fIfile +.B \-c The root update certificate file, that is read in. -Default is @UNBOUND_ROOTCERT_FILE@. +Default is \fB@UNBOUND_ROOTCERT_FILE@\fP\&. If the file does not exist, or is empty, a builtin certificate is used. +.UNINDENT +.INDENT 0.0 .TP .B \-l List the builtin root key and builtin root update certificate on stdout. +.UNINDENT +.INDENT 0.0 .TP -.B \-u \fIname -The server name, it connects to https://name. Specify without https:// prefix. -The default is "data.iana.org". It connects to the port specified with \-P. +.B \-u +The server name, it connects to \fBhttps://name\fP\&. +Specify without \fBhttps://\fP prefix. +The default is \fB\(dqdata.iana.org\(dq\fP\&. +It connects to the port specified with \fI\%\-P\fP\&. You can pass an IPv4 address or IPv6 address (no brackets) if you want. +.UNINDENT +.INDENT 0.0 .TP .B \-S -Do not use SNI for the HTTPS connection. Default is to use SNI. +Do not use SNI for the HTTPS connection. +Default is to use SNI. +.UNINDENT +.INDENT 0.0 .TP -.B \-b \fIaddress -The source address to bind to for domain resolution and contacting the server -on https. May be either an IPv4 address or IPv6 address (no brackets). +.B \-b
+The source address to bind to for domain resolution and contacting the +server on https. +May be either an IPv4 address or IPv6 address (no brackets). +.UNINDENT +.INDENT 0.0 .TP -.B \-x \fIpath -The pathname to the root\-anchors.xml file on the server. (forms URL with \-u). -The default is /root\-anchors/root\-anchors.xml. +.B \-x +The pathname to the root\-anchors.xml file on the server. +(forms URL with \fI\%\-u\fP). +The default is \fB/root\-anchors/root\-anchors.xml\fP\&. +.UNINDENT +.INDENT 0.0 .TP -.B \-s \fIpath -The pathname to the root\-anchors.p7s file on the server. (forms URL with \-u). -The default is /root\-anchors/root\-anchors.p7s. This file has to be a PKCS7 -signature over the xml file, using the pem file (\-c) as trust anchor. +.B \-s +The pathname to the root\-anchors.p7s file on the server. +(forms URL with \fI\%\-u\fP). +The default is \fB/root\-anchors/root\-anchors.p7s\fP\&. +This file has to be a PKCS7 signature over the xml file, using the pem +file (\fI\%\-c\fP) as trust anchor. +.UNINDENT +.INDENT 0.0 .TP -.B \-n \fIname -The emailAddress for the Subject of the signer's certificate from the p7s -signature file. Only signatures from this name are allowed. default is -dnssec@iana.org. If you pass "" then the emailAddress is not checked. +.B \-n +The emailAddress for the Subject of the signer\(aqs certificate from the +p7s signature file. +Only signatures from this name are allowed. +The default is \fBdnssec@iana.org\fP\&. +If you pass \fB\(dq\(dq\fP then the emailAddress is not checked. +.UNINDENT +.INDENT 0.0 .TP .B \-4 -Use IPv4 for domain resolution and contacting the server on https. Default is -to use IPv4 and IPv6 where appropriate. +Use IPv4 for domain resolution and contacting the server on +https. +Default is to use IPv4 and IPv6 where appropriate. +.UNINDENT +.INDENT 0.0 .TP .B \-6 -Use IPv6 for domain resolution and contacting the server on https. Default is -to use IPv4 and IPv6 where appropriate. +Use IPv6 for domain resolution and contacting the server on https. +Default is to use IPv4 and IPv6 where appropriate. +.UNINDENT +.INDENT 0.0 .TP -.B \-f \fIresolv.conf -Use the given resolv.conf file. Not enabled by default, but you could try to -pass /etc/resolv.conf on some systems. It contains the IP addresses of the -recursive nameservers to use. However, since this tool could be used to -bootstrap that very recursive nameserver, it would not be useful (since -that server is not up yet, since we are bootstrapping it). It could be -useful in a situation where you know an upstream cache is deployed (and -running) and in captive portal situations. +.B \-f +Use the given resolv.conf file. +Not enabled by default, but you could try to pass +\fB/etc/resolv.conf\fP on some systems. +It contains the IP addresses of the recursive nameservers to use. +However, since this tool could be used to bootstrap that very recursive +nameserver, it would not be useful (since that server is not up yet, +since we are bootstrapping it). +It could be useful in a situation where you know an upstream cache is +deployed (and running) and in captive portal situations. +.UNINDENT +.INDENT 0.0 .TP -.B \-r \fIroot.hints -Use the given root.hints file (same syntax as the BIND and Unbound root hints -file) to bootstrap domain resolution. By default a list of builtin root -hints is used. Unbound\-anchor goes to the network itself for these roots, -to resolve the server (\-u option) and to check the root DNSKEY records. +.B \-r +Use the given root.hints file (same syntax as the BIND and Unbound root +hints file) to bootstrap domain resolution. +By default a list of builtin root hints is used. +unbound\-anchor goes to the network itself for these roots, to resolve +the server (\fI\%\-u\fP option) and to check the root DNSKEY records. It does so, because the tool when used for bootstrapping the recursive -resolver, cannot use that recursive resolver itself because it is bootstrapping -that server. +resolver, cannot use that recursive resolver itself because it is +bootstrapping that server. +.UNINDENT +.INDENT 0.0 .TP .B \-R -Allow fallback from \-f resolv.conf file to direct root servers query. -It allows you to prefer local resolvers, but fallback automatically -to direct root query if they do not respond or do not support DNSSEC. +Allow fallback from \fI\%\-f\fP \fB\fP file to direct root +servers query. +It allows you to prefer local resolvers, but fallback automatically to +direct root query if they do not respond or do not support DNSSEC. +.UNINDENT +.INDENT 0.0 .TP .B \-v -More verbose. Once prints informational messages, multiple times may enable -large debug amounts (such as full certificates or byte\-dumps of downloaded -files). By default it prints almost nothing. It also prints nothing on -errors by default; in that case the original root anchor file is simply -left undisturbed, so that a recursive server can start right after it. +More verbose. +Once prints informational messages, multiple times may enable large +debug amounts (such as full certificates or byte\-dumps of downloaded +files). +By default it prints almost nothing. +It also prints nothing on errors by default; in that case the original +root anchor file is simply left undisturbed, so that a recursive server +can start right after it. +.UNINDENT +.INDENT 0.0 .TP -.B \-C \fIunbound.conf -Debug option to read unbound.conf into the resolver process used. +.B \-C +Debug option to read \fB\fP into the resolver process +used. +.UNINDENT +.INDENT 0.0 .TP -.B \-P \fIport -Set the port number to use for the https connection. The default is 443. +.B \-P +Set the port number to use for the https connection. +The default is 443. +.UNINDENT +.INDENT 0.0 .TP .B \-F -Debug option to force update of the root anchor through downloading the xml -file and verifying it with the certificate. By default it first tries to -update by contacting the DNS, which uses much less bandwidth, is much -faster (200 msec not 2 sec), and is nicer to the deployed infrastructure. -With this option, it still attempts to do so (and may verbosely tell you), -but then ignores the result and goes on to use the xml fallback method. +Debug option to force update of the root anchor through downloading the +xml file and verifying it with the certificate. +By default it first tries to update by contacting the DNS, which uses +much less bandwidth, is much faster (200 msec not 2 sec), and is nicer +to the deployed infrastructure. +With this option, it still attempts to do so (and may verbosely tell +you), but then ignores the result and goes on to use the xml fallback +method. +.UNINDENT +.INDENT 0.0 .TP .B \-h Show the version and commandline option help. -.SH "EXIT CODE" +.UNINDENT +.SH EXIT CODE +.sp This tool exits with value 1 if the root anchor was updated using the -certificate or if the builtin root-anchor was used. It exits with code -0 if no update was necessary, if the update was possible with RFC5011 -tracking, or if an error occurred. -.P +certificate or if the builtin root\-anchor was used. +It exits with code 0 if no update was necessary, if the update was possible +with \fI\%RFC 5011\fP tracking, or if an error occurred. +.sp You can check the exit value in this manner: +.INDENT 0.0 +.INDENT 3.5 +.sp .nf - unbound-anchor \-a "root.key" || logger "Please check root.key" +.ft C +unbound\-anchor \-a \(dqroot.key\(dq || logger \(dqPlease check root.key\(dq +.ft P .fi +.UNINDENT +.UNINDENT +.sp Or something more suitable for your operational environment. -.SH "TRUST" -The root keys and update certificate included in this tool -are provided for convenience and under the terms of our -license (see the LICENSE file in the source distribution or -https://github.com/NLnetLabs/unbound/blob/master/LICENSE) and might be stale or -not suitable to your purpose. -.P -By running "unbound\-anchor \-l" the keys and certificate that are +.SH TRUST +.sp +The root keys and update certificate included in this tool are provided for +convenience and under the terms of our license (see the LICENSE file in the +source distribution or \fI\%https://github.com/NLnetLabs/unbound/blob/master/LICENSE\fP +and might be stale or not suitable to your purpose. +.sp +By running \fI\%unbound\-anchor \-l\fP the keys and certificate that are configured in the code are printed for your convenience. -.P -The build\-in configuration can be overridden by providing a root\-cert -file and a rootkey file. -.SH "FILES" +.sp +The built\-in configuration can be overridden by providing a root\-cert file and +a rootkey file. +.SH FILES +.INDENT 0.0 .TP -.I @UNBOUND_ROOTKEY_FILE@ -The root anchor file, updated with 5011 tracking, and read and written to. +.B @UNBOUND_ROOTKEY_FILE@ +The root anchor file, updated with 5011 tracking, and read and written +to. The file is created if it does not exist. .TP -.I @UNBOUND_ROOTCERT_FILE@ -The trusted self\-signed certificate that is used to verify the downloaded -DNSSEC root trust anchor. You can update it by fetching it from -https://data.iana.org/root\-anchors/icannbundle.pem (and validate it). +.B @UNBOUND_ROOTCERT_FILE@ +The trusted self\-signed certificate that is used to verify the +downloaded DNSSEC root trust anchor. +You can update it by fetching it from +\fI\%https://data.iana.org/root\-anchors/icannbundle.pem\fP (and validate it). If the file does not exist or is empty, a builtin version is used. .TP -.I https://data.iana.org/root\-anchors/root\-anchors.xml +.B \fI\%https://data.iana.org/root\-anchors/root\-anchors.xml\fP Source for the root key information. .TP -.I https://data.iana.org/root\-anchors/root\-anchors.p7s +.B \fI\%https://data.iana.org/root\-anchors/root\-anchors.p7s\fP Signature on the root key information. -.SH "SEE ALSO" -\fIunbound.conf\fR(5), -\fIunbound\fR(8). +.UNINDENT +.SH SEE ALSO +.sp +\fI\%unbound.conf(5)\fP, +\fI\%unbound(8)\fP\&. +.SH AUTHOR +Unbound developers are mentioned in the CREDITS file in the distribution. +.SH COPYRIGHT +1999-2025, NLnet Labs +.\" Generated by docutils manpage writer. +. diff --git a/doc/unbound-anchor.rst b/doc/unbound-anchor.rst new file mode 100644 index 000000000..480db8eeb --- /dev/null +++ b/doc/unbound-anchor.rst @@ -0,0 +1,281 @@ +.. + WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE + +.. + IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE + +.. + IT HELPS PEOPLE DIFFING THE CHANGES + +.. + WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE + +.. + IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE + +.. + IT HELPS PEOPLE DIFFING THE CHANGES + +.. + WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE + +.. + IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE + +.. + IT HELPS PEOPLE DIFFING THE CHANGES + +.. + WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE + +.. + IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE + +.. + IT HELPS PEOPLE DIFFING THE CHANGES + +.. program:: unbound-anchor + +unbound-anchor(8) +================= + +Synopsis +-------- + +**unbound-anchor** [``opts``] + +Description +----------- + +``unbound-anchor`` performs setup or update of the root trust anchor for DNSSEC +validation. +The program fetches the trust anchor with the method from :rfc:`7958` when +regular :rfc:`5011` update fails to bring it up to date. +It can be run (as root) from the commandline, or run as part of startup +scripts. +Before you start the :doc:`unbound(8)` DNS server. + +Suggested usage: + +.. code-block:: text + + # in the init scripts. + # provide or update the root anchor (if necessary) + unbound-anchor -a "@UNBOUND_ROOTKEY_FILE@" + # Please note usage of this root anchor is at your own risk + # and under the terms of our LICENSE (see source). + # + # start validating resolver + # the unbound.conf contains: + # auto-trust-anchor-file: "@UNBOUND_ROOTKEY_FILE@" + unbound -c unbound.conf + +This tool provides builtin default contents for the root anchor and root update +certificate files. + +It tests if the root anchor file works, and if not, and an update is possible, +attempts to update the root anchor using the root update certificate. +It performs a https fetch of +`root-anchors.xml `__ +and checks the results (:rfc:`7958`); if all checks are successful, it updates +the root anchor file. +Otherwise the root anchor file is unchanged. +It performs :rfc:`5011` tracking if the DNSSEC information available via the +DNS makes that possible. + +It does not perform an update if the certificate is expired, if the network is +down or other errors occur. + +The available options are: + +.. option:: -a + + The root anchor key file, that is read in and written out. + Default is :file:`@UNBOUND_ROOTKEY_FILE@`. + If the file does not exist, or is empty, a builtin root key is written + to it. + +.. option:: -c + + The root update certificate file, that is read in. + Default is :file:`@UNBOUND_ROOTCERT_FILE@`. + If the file does not exist, or is empty, a builtin certificate is used. + +.. option:: -l + + List the builtin root key and builtin root update certificate on stdout. + +.. option:: -u + + The server name, it connects to ``https://name``. + Specify without ``https://`` prefix. + The default is ``"data.iana.org"``. + It connects to the port specified with :option:`-P`. + You can pass an IPv4 address or IPv6 address (no brackets) if you want. + +.. option:: -S + + Do not use SNI for the HTTPS connection. + Default is to use SNI. + +.. option:: -b
+ + The source address to bind to for domain resolution and contacting the + server on https. + May be either an IPv4 address or IPv6 address (no brackets). + +.. option:: -x + + The pathname to the root-anchors.xml file on the server. + (forms URL with :option:`-u`). + The default is :file:`/root-anchors/root-anchors.xml`. + +.. option:: -s + + The pathname to the root-anchors.p7s file on the server. + (forms URL with :option:`-u`). + The default is :file:`/root-anchors/root-anchors.p7s`. + This file has to be a PKCS7 signature over the xml file, using the pem + file (:option:`-c`) as trust anchor. + +.. option:: -n + + The emailAddress for the Subject of the signer's certificate from the + p7s signature file. + Only signatures from this name are allowed. + The default is ``dnssec@iana.org``. + If you pass ``""`` then the emailAddress is not checked. + +.. option:: -4 + + Use IPv4 for domain resolution and contacting the server on + https. + Default is to use IPv4 and IPv6 where appropriate. + +.. option:: -6 + + Use IPv6 for domain resolution and contacting the server on https. + Default is to use IPv4 and IPv6 where appropriate. + +.. option:: -f + + Use the given resolv.conf file. + Not enabled by default, but you could try to pass + :file:`/etc/resolv.conf` on some systems. + It contains the IP addresses of the recursive nameservers to use. + However, since this tool could be used to bootstrap that very recursive + nameserver, it would not be useful (since that server is not up yet, + since we are bootstrapping it). + It could be useful in a situation where you know an upstream cache is + deployed (and running) and in captive portal situations. + +.. option:: -r + + Use the given root.hints file (same syntax as the BIND and Unbound root + hints file) to bootstrap domain resolution. + By default a list of builtin root hints is used. + unbound-anchor goes to the network itself for these roots, to resolve + the server (:option:`-u` option) and to check the root DNSKEY records. + It does so, because the tool when used for bootstrapping the recursive + resolver, cannot use that recursive resolver itself because it is + bootstrapping that server. + +.. option:: -R + + Allow fallback from :option:`-f` ```` file to direct root + servers query. + It allows you to prefer local resolvers, but fallback automatically to + direct root query if they do not respond or do not support DNSSEC. + +.. option:: -v + + More verbose. + Once prints informational messages, multiple times may enable large + debug amounts (such as full certificates or byte-dumps of downloaded + files). + By default it prints almost nothing. + It also prints nothing on errors by default; in that case the original + root anchor file is simply left undisturbed, so that a recursive server + can start right after it. + +.. option:: -C + + Debug option to read :file:`` into the resolver process + used. + +.. option:: -P + + Set the port number to use for the https connection. + The default is 443. + +.. option:: -F + + Debug option to force update of the root anchor through downloading the + xml file and verifying it with the certificate. + By default it first tries to update by contacting the DNS, which uses + much less bandwidth, is much faster (200 msec not 2 sec), and is nicer + to the deployed infrastructure. + With this option, it still attempts to do so (and may verbosely tell + you), but then ignores the result and goes on to use the xml fallback + method. + +.. option:: -h + + Show the version and commandline option help. + +Exit Code +--------- + +This tool exits with value 1 if the root anchor was updated using the +certificate or if the builtin root-anchor was used. +It exits with code 0 if no update was necessary, if the update was possible +with :rfc:`5011` tracking, or if an error occurred. + +You can check the exit value in this manner: + +.. code-block:: text + + unbound-anchor -a "root.key" || logger "Please check root.key" + +Or something more suitable for your operational environment. + +Trust +----- + +The root keys and update certificate included in this tool are provided for +convenience and under the terms of our license (see the LICENSE file in the +source distribution or https://github.com/NLnetLabs/unbound/blob/master/LICENSE +and might be stale or not suitable to your purpose. + +By running :option:`unbound-anchor -l` the keys and certificate that are +configured in the code are printed for your convenience. + +The built-in configuration can be overridden by providing a root-cert file and +a rootkey file. + +Files +----- + +@UNBOUND_ROOTKEY_FILE@ + The root anchor file, updated with 5011 tracking, and read and written + to. + The file is created if it does not exist. + +@UNBOUND_ROOTCERT_FILE@ + The trusted self-signed certificate that is used to verify the + downloaded DNSSEC root trust anchor. + You can update it by fetching it from + https://data.iana.org/root-anchors/icannbundle.pem (and validate it). + If the file does not exist or is empty, a builtin version is used. + +https://data.iana.org/root-anchors/root-anchors.xml + Source for the root key information. + +https://data.iana.org/root-anchors/root-anchors.p7s + Signature on the root key information. + +See Also +-------- + +:doc:`unbound.conf(5)`, +:doc:`unbound(8)`. diff --git a/doc/unbound-checkconf.8.in b/doc/unbound-checkconf.8.in index f4d0f2908..1d17573e9 100644 --- a/doc/unbound-checkconf.8.in +++ b/doc/unbound-checkconf.8.in @@ -1,56 +1,93 @@ -.TH "unbound-checkconf" "8" "@date@" "NLnet Labs" "unbound @version@" -.\" -.\" unbound-checkconf.8 -- unbound configuration checker manual -.\" -.\" Copyright (c) 2007, NLnet Labs. All rights reserved. -.\" -.\" See LICENSE for the license. -.\" -.\" -.SH "NAME" -unbound\-checkconf -\- Check Unbound configuration file for errors. -.SH "SYNOPSIS" -.B unbound\-checkconf -.RB [ \-h ] -.RB [ \-f ] -.RB [ \-q ] -.RB [ \-o -.IR option ] -.RI [ cfgfile ] -.SH "DESCRIPTION" -.B Unbound\-checkconf -checks the configuration file for the -\fIunbound\fR(8) -DNS resolver for syntax and other errors. +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "UNBOUND-CHECKCONF" "8" "@date@" "@version@" "Unbound" +.SH NAME +unbound-checkconf \- Check Unbound @version@ configuration file for errors. +.SH SYNOPSIS +.sp +\fBunbound\-checkconf\fP [\fB\-hf\fP] [\fB\-o option\fP] [cfgfile] +.SH DESCRIPTION +.sp +\fBunbound\-checkconf\fP checks the configuration file for the +\fI\%unbound(8)\fP DNS resolver for syntax and other errors. The config file syntax is described in -\fIunbound.conf\fR(5). -.P +\fI\%unbound.conf(5)\fP\&. +.sp The available options are: +.INDENT 0.0 .TP .B \-h Show the version and commandline option help. +.UNINDENT +.INDENT 0.0 .TP .B \-f -Print full pathname, with chroot applied to it. Use with the \-o option. -.TP -.B \-o\fI option -If given, after checking the config file the value of this option is -printed to stdout. For "" (disabled) options an empty line is printed. +Print full pathname, with chroot applied to it. +Use with the \fI\%\-o\fP option. +.UNINDENT +.INDENT 0.0 .TP .B \-q Make the operation quiet, suppress output on success. +.UNINDENT +.INDENT 0.0 .TP -.I cfgfile -The config file to read with settings for Unbound. It is checked. +.B \-o