From 5758427d86a6a11906000866611a79ac325226c1 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 1 Aug 2025 10:34:12 +0200 Subject: [PATCH 01/23] - Fix testbound test program to accurately output packets from hex. --- doc/Changelog | 3 +++ testcode/fake_event.c | 26 ++++++++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 10b5d95d8..883920b77 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +1 August 2025: Wouter + - Fix testbound test program to accurately output packets from hex. + 28 July 2025: Wouter - Fix redis cachedb module gettimeofday init failure. diff --git a/testcode/fake_event.c b/testcode/fake_event.c index ea05ea1ed..ce439edd1 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -188,6 +188,22 @@ delete_replay_answer(struct replay_answer* a) free(a); } +/** Log the packet for a reply_packet from testpkts. */ +static void +log_testpkt_reply_pkt(const char* txt, struct reply_packet* reppkt) +{ + if(!reppkt) { + log_info("%s ", txt); + return; + } + if(reppkt->reply_from_hex) { + log_pkt(txt, sldns_buffer_begin(reppkt->reply_from_hex), + sldns_buffer_limit(reppkt->reply_from_hex)); + return; + } + log_pkt(txt, reppkt->reply_pkt, reppkt->reply_len); +} + /** * return: true if pending query matches the now event. */ @@ -240,9 +256,8 @@ pending_find_match(struct replay_runtime* runtime, struct entry** entry, p->start_step, p->end_step, (*entry)->lineno); if(p->addrlen != 0) log_addr(0, "matched ip", &p->addr, p->addrlen); - log_pkt("matched pkt: ", - (*entry)->reply_list->reply_pkt, - (*entry)->reply_list->reply_len); + log_testpkt_reply_pkt("matched pkt: ", + (*entry)->reply_list); return 1; } p = p->next_range; @@ -330,7 +345,7 @@ fill_buffer_with_reply(sldns_buffer* buffer, struct entry* entry, uint8_t* q, while(reppkt && i--) reppkt = reppkt->next; if(!reppkt) fatal_exit("extra packet read from TCP stream but none is available"); - log_pkt("extra_packet ", reppkt->reply_pkt, reppkt->reply_len); + log_testpkt_reply_pkt("extra packet ", reppkt); } if(reppkt->reply_from_hex) { c = sldns_buffer_begin(reppkt->reply_from_hex); @@ -462,8 +477,7 @@ fake_front_query(struct replay_runtime* runtime, struct replay_moment *todo) repinfo.c->type = comm_udp; fill_buffer_with_reply(repinfo.c->buffer, todo->match, NULL, 0, 0); log_info("testbound: incoming QUERY"); - log_pkt("query pkt", todo->match->reply_list->reply_pkt, - todo->match->reply_list->reply_len); + log_testpkt_reply_pkt("query pkt ", todo->match->reply_list); /* call the callback for incoming queries */ if((*runtime->callback_query)(repinfo.c, runtime->cb_arg, NETEVENT_NOERROR, &repinfo)) { From da6b735ed9f2d5d7259ae5130f26a49bc526c632 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 5 Aug 2025 15:46:54 +0200 Subject: [PATCH 02/23] - Fix #1309: incorrectly reclaimed tcp handler can cause data corruption and segfault. --- doc/Changelog | 4 ++++ util/netevent.c | 48 ++++++++++++++++++++++++++++++++++++++---------- util/netevent.h | 2 ++ 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 883920b77..e1d3e5b45 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +5 August 2025: Wouter + - Fix #1309: incorrectly reclaimed tcp handler can cause data + corruption and segfault. + 1 August 2025: Wouter - Fix testbound test program to accurately output packets from hex. diff --git a/util/netevent.c b/util/netevent.c index 8d6445abf..952efc111 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -3218,6 +3218,10 @@ comm_point_tcp_accept_callback(int fd, short event, void* arg) } /* accept incoming connection. */ c_hdl = c->tcp_free; + if(!c_hdl->is_in_tcp_free) { + /* Should not happen */ + fatal_exit("inconsistent tcp_free state in accept_callback"); + } /* clear leftover flags from previous use, and then set the * correct event base for the event structure for libevent */ ub_event_free(c_hdl->ev->ev); @@ -3292,10 +3296,16 @@ comm_point_tcp_accept_callback(int fd, short event, void* arg) #endif } + /* Paranoia: Check that the state has not changed from above: */ + if(c_hdl != c->tcp_free || !c_hdl->is_in_tcp_free) { + /* Should not happen */ + fatal_exit("tcp_free state changed within accept_callback!"); + } /* grab the tcp handler buffers */ c->cur_tcp_count++; c->tcp_free = c_hdl->tcp_free; c_hdl->tcp_free = NULL; + c_hdl->is_in_tcp_free = 0; if(!c->tcp_free) { /* stop accepting incoming queries for now. */ comm_point_stop_listening(c); @@ -3316,12 +3326,15 @@ reclaim_tcp_handler(struct comm_point* c) #endif } comm_point_close(c); - if(c->tcp_parent) { - if(c != c->tcp_parent->tcp_free) { - c->tcp_parent->cur_tcp_count--; - c->tcp_free = c->tcp_parent->tcp_free; - c->tcp_parent->tcp_free = c; + if(c->tcp_parent && !c->is_in_tcp_free) { + if(c->tcp_free || c->tcp_parent->cur_tcp_count <= 0) { + /* Should not happen */ + fatal_exit("bad tcp_free state in reclaim_tcp"); } + c->tcp_parent->cur_tcp_count--; + c->tcp_free = c->tcp_parent->tcp_free; + c->tcp_parent->tcp_free = c; + c->is_in_tcp_free = 1; if(!c->tcp_free) { /* re-enable listening on accept socket */ comm_point_start_listening(c->tcp_parent, -1, -1); @@ -4707,12 +4720,15 @@ reclaim_http_handler(struct comm_point* c) #endif } comm_point_close(c); - if(c->tcp_parent) { - if(c != c->tcp_parent->tcp_free) { - c->tcp_parent->cur_tcp_count--; - c->tcp_free = c->tcp_parent->tcp_free; - c->tcp_parent->tcp_free = c; + if(c->tcp_parent && !c->is_in_tcp_free) { + if(c->tcp_free || c->tcp_parent->cur_tcp_count <= 0) { + /* Should not happen */ + fatal_exit("bad tcp_free state in reclaim_http"); } + c->tcp_parent->cur_tcp_count--; + c->tcp_free = c->tcp_parent->tcp_free; + c->tcp_parent->tcp_free = c; + c->is_in_tcp_free = 1; if(!c->tcp_free) { /* re-enable listening on accept socket */ comm_point_start_listening(c->tcp_parent, -1, -1); @@ -5748,6 +5764,7 @@ comm_point_create_udp(struct comm_base *base, int fd, sldns_buffer* buffer, c->cur_tcp_count = 0; c->tcp_handlers = NULL; c->tcp_free = NULL; + c->is_in_tcp_free = 0; c->type = comm_udp; c->tcp_do_close = 0; c->do_not_close = 0; @@ -5812,6 +5829,7 @@ comm_point_create_udp_ancil(struct comm_base *base, int fd, c->cur_tcp_count = 0; c->tcp_handlers = NULL; c->tcp_free = NULL; + c->is_in_tcp_free = 0; c->type = comm_udp; c->tcp_do_close = 0; c->do_not_close = 0; @@ -5879,6 +5897,7 @@ comm_point_create_doq(struct comm_base *base, int fd, sldns_buffer* buffer, c->cur_tcp_count = 0; c->tcp_handlers = NULL; c->tcp_free = NULL; + c->is_in_tcp_free = 0; c->type = comm_doq; c->tcp_do_close = 0; c->do_not_close = 0; @@ -5979,6 +5998,7 @@ comm_point_create_tcp_handler(struct comm_base *base, c->cur_tcp_count = 0; c->tcp_handlers = NULL; c->tcp_free = NULL; + c->is_in_tcp_free = 0; c->type = comm_tcp; c->tcp_do_close = 0; c->do_not_close = 0; @@ -6016,6 +6036,7 @@ comm_point_create_tcp_handler(struct comm_base *base, /* add to parent free list */ c->tcp_free = parent->tcp_free; parent->tcp_free = c; + c->is_in_tcp_free = 1; /* ub_event stuff */ evbits = UB_EV_PERSIST | UB_EV_READ | UB_EV_TIMEOUT; c->ev->ev = ub_event_new(base->eb->base, c->fd, evbits, @@ -6078,6 +6099,7 @@ comm_point_create_http_handler(struct comm_base *base, c->cur_tcp_count = 0; c->tcp_handlers = NULL; c->tcp_free = NULL; + c->is_in_tcp_free = 0; c->type = comm_http; c->tcp_do_close = 1; c->do_not_close = 0; @@ -6136,6 +6158,7 @@ comm_point_create_http_handler(struct comm_base *base, /* add to parent free list */ c->tcp_free = parent->tcp_free; parent->tcp_free = c; + c->is_in_tcp_free = 1; /* ub_event stuff */ evbits = UB_EV_PERSIST | UB_EV_READ | UB_EV_TIMEOUT; c->ev->ev = ub_event_new(base->eb->base, c->fd, evbits, @@ -6197,6 +6220,7 @@ comm_point_create_tcp(struct comm_base *base, int fd, int num, return NULL; } c->tcp_free = NULL; + c->is_in_tcp_free = 0; c->type = comm_tcp_accept; c->tcp_do_close = 0; c->do_not_close = 0; @@ -6291,6 +6315,7 @@ comm_point_create_tcp_out(struct comm_base *base, size_t bufsize, c->cur_tcp_count = 0; c->tcp_handlers = NULL; c->tcp_free = NULL; + c->is_in_tcp_free = 0; c->type = comm_tcp; c->tcp_do_close = 0; c->do_not_close = 0; @@ -6355,6 +6380,7 @@ comm_point_create_http_out(struct comm_base *base, size_t bufsize, c->cur_tcp_count = 0; c->tcp_handlers = NULL; c->tcp_free = NULL; + c->is_in_tcp_free = 0; c->type = comm_http; c->tcp_do_close = 0; c->do_not_close = 0; @@ -6425,6 +6451,7 @@ comm_point_create_local(struct comm_base *base, int fd, size_t bufsize, c->cur_tcp_count = 0; c->tcp_handlers = NULL; c->tcp_free = NULL; + c->is_in_tcp_free = 0; c->type = comm_local; c->tcp_do_close = 0; c->do_not_close = 1; @@ -6488,6 +6515,7 @@ comm_point_create_raw(struct comm_base* base, int fd, int writing, c->cur_tcp_count = 0; c->tcp_handlers = NULL; c->tcp_free = NULL; + c->is_in_tcp_free = 0; c->type = comm_raw; c->tcp_do_close = 0; c->do_not_close = 1; diff --git a/util/netevent.h b/util/netevent.h index 96de0032c..f0f336e43 100644 --- a/util/netevent.h +++ b/util/netevent.h @@ -238,6 +238,8 @@ struct comm_point { /** linked list of free tcp_handlers to use for new queries. For tcp_accept the first entry, for tcp_handlers the next one. */ struct comm_point* tcp_free; + /** Whether this struct is in its parent's tcp_free list */ + int is_in_tcp_free; /* -------- SSL TCP DNS ------- */ /** the SSL object with rw bio (owned) or for commaccept ctx ref */ From 3d7e847a5ec89cb1d35844674a24f2bf0ae51e81 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 5 Aug 2025 16:20:01 +0200 Subject: [PATCH 03/23] - Fix to use assertions for consistency checks in #1309 reclaimed --- doc/Changelog | 2 ++ util/netevent.c | 28 ++++++++++++---------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index e1d3e5b45..e42128f1c 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,8 @@ 5 August 2025: Wouter - Fix #1309: incorrectly reclaimed tcp handler can cause data corruption and segfault. + - Fix to use assertions for consistency checks in #1309 reclaimed + tcp handlers. 1 August 2025: Wouter - Fix testbound test program to accurately output packets from hex. diff --git a/util/netevent.c b/util/netevent.c index 952efc111..0756dc26c 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -3218,10 +3218,9 @@ comm_point_tcp_accept_callback(int fd, short event, void* arg) } /* accept incoming connection. */ c_hdl = c->tcp_free; - if(!c_hdl->is_in_tcp_free) { - /* Should not happen */ - fatal_exit("inconsistent tcp_free state in accept_callback"); - } + /* Should not happen: inconsistent tcp_free state in + * accept_callback. */ + log_assert(c_hdl->is_in_tcp_free); /* clear leftover flags from previous use, and then set the * correct event base for the event structure for libevent */ ub_event_free(c_hdl->ev->ev); @@ -3297,10 +3296,9 @@ comm_point_tcp_accept_callback(int fd, short event, void* arg) } /* Paranoia: Check that the state has not changed from above: */ - if(c_hdl != c->tcp_free || !c_hdl->is_in_tcp_free) { - /* Should not happen */ - fatal_exit("tcp_free state changed within accept_callback!"); - } + /* Should not happen: tcp_free state changed within accept_callback. */ + log_assert(c_hdl == c->tcp_free); + log_assert(c_hdl->is_in_tcp_free); /* grab the tcp handler buffers */ c->cur_tcp_count++; c->tcp_free = c_hdl->tcp_free; @@ -3327,10 +3325,9 @@ reclaim_tcp_handler(struct comm_point* c) } comm_point_close(c); if(c->tcp_parent && !c->is_in_tcp_free) { - if(c->tcp_free || c->tcp_parent->cur_tcp_count <= 0) { - /* Should not happen */ - fatal_exit("bad tcp_free state in reclaim_tcp"); - } + /* Should not happen: bad tcp_free state in reclaim_tcp. */ + log_assert(c->tcp_free == NULL); + log_assert(c->tcp_parent->cur_tcp_count > 0); c->tcp_parent->cur_tcp_count--; c->tcp_free = c->tcp_parent->tcp_free; c->tcp_parent->tcp_free = c; @@ -4721,10 +4718,9 @@ reclaim_http_handler(struct comm_point* c) } comm_point_close(c); if(c->tcp_parent && !c->is_in_tcp_free) { - if(c->tcp_free || c->tcp_parent->cur_tcp_count <= 0) { - /* Should not happen */ - fatal_exit("bad tcp_free state in reclaim_http"); - } + /* Should not happen: bad tcp_free state in reclaim_http. */ + log_assert(c->tcp_free == NULL); + log_assert(c->tcp_parent->cur_tcp_count > 0); c->tcp_parent->cur_tcp_count--; c->tcp_free = c->tcp_parent->tcp_free; c->tcp_parent->tcp_free = c; From 305adf12bfa580552faa61af51ff5e17556015b7 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 6 Aug 2025 12:01:42 +0200 Subject: [PATCH 04/23] - Fix edns subnet, so that the subquery without subnet is stored in global cache if the querier used 0.0.0.0/0 and the name and address do not receive subnet treatment. If the name and address are configured for subnet, it is stored in the subnet cache. --- edns-subnet/subnetmod.c | 43 ++++++++++++++++++++++++++++++++++------- edns-subnet/subnetmod.h | 4 ++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/edns-subnet/subnetmod.c b/edns-subnet/subnetmod.c index c763c5f1a..6f71129d9 100644 --- a/edns-subnet/subnetmod.c +++ b/edns-subnet/subnetmod.c @@ -154,6 +154,21 @@ int ecs_whitelist_check(struct query_info* qinfo, return 1; sn_env = (struct subnet_env*)qstate->env->modinfo[id]; + if(sq->is_subquery_nonsubnet) { + if(sq->is_subquery_scopezero) { + /* Check if the result can be stored in the global cache, + * this is okay if the address and name are not configured + * as subnet address and subnet zone. */ + if(!ecs_is_whitelisted(sn_env->whitelist, + addr, addrlen, qinfo->qname, qinfo->qname_len, + qinfo->qclass)) { + verbose(VERB_ALGO, "subnet store subquery global, name and addr have no subnet treatment."); + qstate->no_cache_store = 0; + } + } + return 1; + } + /* Cache by default, might be disabled after parsing EDNS option * received from nameserver. */ if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL, NULL, 0) @@ -527,11 +542,12 @@ common_prefix(uint8_t *a, uint8_t *b, uint8_t net) /** * Create sub request that looks up the query. * @param qstate: query state + * @param id: module id. * @param sq: subnet qstate * @return false on failure. */ static int -generate_sub_request(struct module_qstate *qstate, struct subnet_qstate* sq) +generate_sub_request(struct module_qstate *qstate, int id, struct subnet_qstate* sq) { struct module_qstate* subq = NULL; uint16_t qflags = 0; /* OPCODE QUERY, no flags */ @@ -557,10 +573,22 @@ generate_sub_request(struct module_qstate *qstate, struct subnet_qstate* sq) } if(subq) { /* It is possible to access the subquery module state. */ + struct subnet_qstate* subsq; + if(!subnet_new_qstate(subq, id)) { + verbose(VERB_ALGO, "Could not allocate new subnet qstate"); + return 0; + } + subsq = (struct subnet_qstate*)subq->minfo[id]; + subsq->is_subquery_nonsubnet = 1; + + /* When the client asks 0.0.0.0/0 and the name is not treated + * as subnet, it is to be stored in the global cache. + * Store that the client asked for that, if so. */ if(sq->ecs_client_in.subnet_source_mask == 0 && edns_opt_list_find(qstate->edns_opts_front_in, qstate->env->cfg->client_subnet_opcode)) { subq->no_cache_store = 1; + subsq->is_subquery_scopezero = 1; } } return 1; @@ -569,15 +597,16 @@ generate_sub_request(struct module_qstate *qstate, struct subnet_qstate* sq) /** * Perform the query without subnet * @param qstate: query state + * @param id: module id. * @param sq: subnet qstate * @return module state */ static enum module_ext_state -generate_lookup_without_subnet(struct module_qstate *qstate, +generate_lookup_without_subnet(struct module_qstate *qstate, int id, struct subnet_qstate* sq) { verbose(VERB_ALGO, "subnetcache: make subquery to look up without subnet"); - if(!generate_sub_request(qstate, sq)) { + if(!generate_sub_request(qstate, id, sq)) { verbose(VERB_ALGO, "Could not generate sub query"); qstate->return_rcode = LDNS_RCODE_FORMERR; qstate->return_msg = NULL; @@ -622,7 +651,7 @@ eval_response(struct module_qstate *qstate, int id, struct subnet_qstate *sq) * is still useful to put it in the edns subnet cache for * when a client explicitly asks for subnet specific answer. */ verbose(VERB_QUERY, "subnetcache: Authority indicates no support"); - return generate_lookup_without_subnet(qstate, sq); + return generate_lookup_without_subnet(qstate, id, sq); } /* Purposefully there was no sent subnet, and there is consequently @@ -654,7 +683,7 @@ eval_response(struct module_qstate *qstate, int id, struct subnet_qstate *sq) qstate->env->cfg->client_subnet_opcode); sq->subnet_sent = 0; sq->subnet_sent_no_subnet = 0; - return generate_lookup_without_subnet(qstate, sq); + return generate_lookup_without_subnet(qstate, id, sq); } lock_rw_wrlock(&sne->biglock); @@ -945,7 +974,7 @@ subnetmod_operate(struct module_qstate *qstate, enum module_ev event, /* aggregated this deaggregated state */ qstate->ext_state[id] = generate_lookup_without_subnet( - qstate, sq); + qstate, id, sq); return; } verbose(VERB_ALGO, "subnetcache: pass to next module"); @@ -993,7 +1022,7 @@ subnetmod_operate(struct module_qstate *qstate, enum module_ev event, qstate->env->cfg->client_subnet_opcode)) { /* client asked for resolution without edns subnet */ qstate->ext_state[id] = generate_lookup_without_subnet( - qstate, sq); + qstate, id, sq); return; } diff --git a/edns-subnet/subnetmod.h b/edns-subnet/subnetmod.h index 3893820fa..d2d9e957f 100644 --- a/edns-subnet/subnetmod.h +++ b/edns-subnet/subnetmod.h @@ -106,6 +106,10 @@ struct subnet_qstate { int wait_subquery; /** The subquery waited for is done. */ int wait_subquery_done; + /** The subnet state is a subquery state for nonsubnet lookup. */ + int is_subquery_nonsubnet; + /** This is a subquery, and it is made due to a scope zero request. */ + int is_subquery_scopezero; }; void subnet_data_delete(void* d, void* ATTR_UNUSED(arg)); From cdcc0337d12f0622ddd10b6a3f5852670eed9a8d Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 6 Aug 2025 12:08:44 +0200 Subject: [PATCH 05/23] - Fix edns subnet, so that the subquery without subnet is stored in global cache if the querier used 0.0.0.0/0 and the name and address do not receive subnet treatment. If the name and address are configured for subnet, it is stored in the subnet cache. --- doc/Changelog | 6 + testdata/subnet_scopezero_global.crpl | 280 ++++++++++++++++++++++++++ 2 files changed, 286 insertions(+) create mode 100644 testdata/subnet_scopezero_global.crpl diff --git a/doc/Changelog b/doc/Changelog index e42128f1c..82e4f61ff 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,9 @@ +6 August 2025: Wouter + - Fix edns subnet, so that the subquery without subnet is stored in + global cache if the querier used 0.0.0.0/0 and the name and address + do not receive subnet treatment. If the name and address are + configured for subnet, it is stored in the subnet cache. + 5 August 2025: Wouter - Fix #1309: incorrectly reclaimed tcp handler can cause data corruption and segfault. diff --git a/testdata/subnet_scopezero_global.crpl b/testdata/subnet_scopezero_global.crpl new file mode 100644 index 000000000..1db7cc322 --- /dev/null +++ b/testdata/subnet_scopezero_global.crpl @@ -0,0 +1,280 @@ +; config options +server: + target-fetch-policy: "0 0 0 0 0" + module-config: "subnetcache validator iterator" + verbosity: 4 + qname-minimisation: no + ; the domain is not configured for edns-subnet + ;send-client-subnet: 1.2.3.4 + client-subnet-zone: "ex2.com" + +stub-zone: + name: "." + stub-addr: 193.0.14.129 + +stub-zone: + name: "example.com" + stub-addr: 1.2.3.4 +stub-zone: + name: "ex2.com" + stub-addr: 1.2.3.5 +CONFIG_END + +SCENARIO_BEGIN Test subnet cache with scope zero for global cache store. + +; the upstream server. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 + +ENTRY_BEGIN +MATCH opcode qtype qname ednsdata +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +HEX_EDNSDATA_BEGIN + ;; we expect to receive empty +HEX_EDNSDATA_END +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END +RANGE_END + +RANGE_BEGIN 0 21 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +SECTION AUTHORITY +SECTION ADDITIONAL +ENTRY_END +RANGE_END + +RANGE_BEGIN 20 61 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.ex2.com. IN A +SECTION ANSWER +www.ex2.com. IN A 10.20.30.41 +SECTION AUTHORITY +SECTION ADDITIONAL +ENTRY_END +RANGE_END + +RANGE_BEGIN 90 101 + ADDRESS 1.2.3.5 +ENTRY_BEGIN + MATCH opcode qtype qname ednsdata + ADJUST copy_id copy_ednsdata_assume_clientsubnet + REPLY QR NOERROR + SECTION QUESTION + www.ex2.com. IN A + SECTION ANSWER + www.ex2.com. 10 IN A 10.20.30.42 + SECTION AUTHORITY + ex2.com. IN NS ns.ex2.com. + SECTION ADDITIONAL + HEX_EDNSDATA_BEGIN + ; client is 127.0.0.1 + 00 08 ; OPC + 00 07 ; option length + 00 01 ; Family + 18 00 ; source mask, scopemask + 7f 00 00 ; address + HEX_EDNSDATA_END + ns.ex2.com. IN A 1.2.3.5 +ENTRY_END +RANGE_END + +; query for 0.0.0.0/0 +STEP 10 QUERY +ENTRY_BEGIN +HEX_ANSWER_BEGIN + 00 00 01 00 00 01 00 00 ;ID 0 + 00 00 00 01 03 77 77 77 ; www.example.com A? (DO) + 07 65 78 61 6d 70 6c 65 + 03 63 6f 6d 00 00 01 00 + 01 00 00 29 10 00 00 00 + 80 00 00 08 + + 00 08 00 04 ; OPC, optlen + 00 01 00 00 ; ip4, scope 0, source 0 + ;0.0.0.0/0 +HEX_ANSWER_END +ENTRY_END + +STEP 20 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ednsdata +REPLY QR RD RA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +SECTION AUTHORITY +SECTION ADDITIONAL +HEX_EDNSDATA_BEGIN + 00 08 ; OPC + 00 04 ; option length + 00 01 ; Family + 00 00 ; source mask, scopemask + ; address +HEX_EDNSDATA_END +ENTRY_END + +; That that it is in global cache. +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD NOERROR +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +STEP 40 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ednsdata +REPLY QR RD RA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +ENTRY_END + +; With a query where the name is whitelisted, it should not be stored +; in global cache. +STEP 50 QUERY +ENTRY_BEGIN +HEX_ANSWER_BEGIN + 00 00 01 00 00 01 00 00 ;ID 0 + 00 00 00 01 03 77 77 77 ; www.ex2.com A? (DO) + 03 65 78 32 03 63 6f 6d + 00 00 01 00 01 00 00 29 + 10 00 00 00 80 00 00 08 + + 00 08 00 04 ; OPC, optlen + 00 01 00 00 ; ip4, scope 0, source 0 + ;0.0.0.0/0 +HEX_ANSWER_END +ENTRY_END + +STEP 60 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ednsdata +REPLY QR RD RA NOERROR +SECTION QUESTION +www.ex2.com. IN A +SECTION ANSWER +www.ex2.com. IN A 10.20.30.41 +SECTION AUTHORITY +SECTION ADDITIONAL +HEX_EDNSDATA_BEGIN + 00 08 ; OPC + 00 04 ; option length + 00 01 ; Family + 00 00 ; source mask, scopemask + ; address +HEX_EDNSDATA_END +ENTRY_END + +STEP 70 QUERY +ENTRY_BEGIN +HEX_ANSWER_BEGIN + 00 00 01 00 00 01 00 00 ;ID 0 + 00 00 00 01 03 77 77 77 ; www.ex2.com A? (DO) + 03 65 78 32 03 63 6f 6d + 00 00 01 00 01 00 00 29 + 10 00 00 00 80 00 00 08 + + 00 08 00 04 ; OPC, optlen + 00 01 00 00 ; ip4, scope 0, source 0 + ;0.0.0.0/0 +HEX_ANSWER_END +ENTRY_END + +STEP 80 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ednsdata +REPLY QR RD RA NOERROR +SECTION QUESTION +www.ex2.com. IN A +SECTION ANSWER +www.ex2.com. IN A 10.20.30.41 +SECTION AUTHORITY +SECTION ADDITIONAL +HEX_EDNSDATA_BEGIN + 00 08 ; OPC + 00 04 ; option length + 00 01 ; Family + 00 00 ; source mask, scopemask + ; address +HEX_EDNSDATA_END +ENTRY_END + +; www.ex2.com is not in the global cache. and gets subnet treatment +STEP 90 QUERY +ENTRY_BEGIN +REPLY RD NOERROR +SECTION QUESTION +www.ex2.com. IN A +ENTRY_END + +STEP 100 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ednsdata +REPLY QR RD RA NOERROR +SECTION QUESTION +www.ex2.com. IN A +SECTION ANSWER +www.ex2.com. IN A 10.20.30.42 +ENTRY_END + +; that result is in the subnet cache +STEP 110 QUERY +ENTRY_BEGIN +HEX_ANSWER_BEGIN + 00 00 01 00 00 01 00 00 ;ID 0 + 00 00 00 01 03 77 77 77 ; www.ex2.com A? (DO) + 03 65 78 32 03 63 6f 6d + 00 00 01 00 01 00 00 29 + 10 00 00 00 80 00 00 0b + + 00 08 00 07 ; OPC, optlen + ; ip4 127.0.0.0/24 scope /0 + 00 01 ; Family + 18 00 ; source mask, scopemask + 7f 00 00 ; address +HEX_ANSWER_END +ENTRY_END + +STEP 120 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ednsdata +REPLY QR RD RA NOERROR +SECTION QUESTION +www.ex2.com. IN A +SECTION ANSWER +www.ex2.com. IN A 10.20.30.42 +SECTION AUTHORITY +SECTION ADDITIONAL +HEX_EDNSDATA_BEGIN + 00 08 ; OPC + 00 07 ; option length + ; ip4 127.0.0.0/24 scope /24 + 00 01 ; Family + 18 18 ; source mask, scopemask + 7f 00 00 ; address +HEX_EDNSDATA_END +ENTRY_END + +SCENARIO_END From 08d59c9a78992fd54bc09be6c13da08d81bc5abe Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 7 Aug 2025 09:45:02 +0200 Subject: [PATCH 06/23] - Fix dname_str for printout of long names. Thanks to Jan Komissar for the fix. --- Makefile.in | 2 +- doc/Changelog | 4 + testcode/unitdname.c | 259 +++++++++++++++++++++++++++++++++++++++++++ util/data/dname.c | 24 ++-- 4 files changed, 275 insertions(+), 14 deletions(-) diff --git a/Makefile.in b/Makefile.in index 76ad6bcb9..d0b3b94d3 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1198,7 +1198,7 @@ unitanchor.lo unitanchor.o: $(srcdir)/testcode/unitanchor.c config.h $(srcdir)/u $(srcdir)/validator/val_anchor.h $(srcdir)/util/rbtree.h $(srcdir)/sldns/sbuffer.h $(srcdir)/sldns/rrdef.h unitdname.lo unitdname.o: $(srcdir)/testcode/unitdname.c config.h $(srcdir)/util/log.h $(srcdir)/testcode/unitmain.h \ $(srcdir)/util/data/dname.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/sldns/sbuffer.h \ - $(srcdir)/sldns/str2wire.h $(srcdir)/sldns/rrdef.h + $(srcdir)/sldns/str2wire.h $(srcdir)/sldns/rrdef.h $(srcdir)/sldns/wire2str.h unitlruhash.lo unitlruhash.o: $(srcdir)/testcode/unitlruhash.c config.h $(srcdir)/testcode/unitmain.h \ $(srcdir)/util/log.h $(srcdir)/util/storage/lruhash.h $(srcdir)/util/locks.h $(srcdir)/util/storage/slabhash.h unitmain.lo unitmain.o: $(srcdir)/testcode/unitmain.c config.h $(srcdir)/sldns/rrdef.h $(srcdir)/sldns/keyraw.h \ diff --git a/doc/Changelog b/doc/Changelog index 82e4f61ff..14955de93 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +7 August 2025: Wouter + - Fix dname_str for printout of long names. Thanks to Jan Komissar + for the fix. + 6 August 2025: Wouter - Fix edns subnet, so that the subquery without subnet is stored in global cache if the querier used 0.0.0.0/0 and the name and address diff --git a/testcode/unitdname.c b/testcode/unitdname.c index 32a9a5fdc..95c6e1fda 100644 --- a/testcode/unitdname.c +++ b/testcode/unitdname.c @@ -45,6 +45,7 @@ #include "util/data/dname.h" #include "sldns/sbuffer.h" #include "sldns/str2wire.h" +#include "sldns/wire2str.h" /** put dname into buffer */ static sldns_buffer* @@ -876,6 +877,262 @@ dname_setup_bufs(sldns_buffer* loopbuf, sldns_buffer* boundbuf) sldns_buffer_flip(boundbuf); } +/* Test strings for the test_long_names test. */ +/* Each label begins with the length of the label including the length octet. */ + +char desc_1[] = "Domain is 1 octet too long."; + +uint8_t wire_dom_1[] = { /* Bad: Domain: (8x)0031abcdefghijklmnopqrstuvwxyz.0007ab. */ + 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, + 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, + 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, + 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, + 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, + 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, + 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, /* Bad: */ 0x06, 0x30, 0x30, 0x30, 0x37, 0x61, 0x62, 0x00 +}; + +char desc_2[] = "Domain has the maximum allowed length (255)."; + +uint8_t wire_dom_2[] = { /* Good: Domain: (8x)0031abcdefghijklmnopqrstuvwxyz.00076a. */ + 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, + 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, + 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, + 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, + 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, + 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, + 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, /* Good: */ 0x05, 0x30, 0x30, 0x30, 0x36, 0x61, 0x00 +}; + +char desc_3[] = "Domain has a length one label in the 255th position for a total of 257."; + +uint8_t wire_dom_3[] = { /* Bad: Domain: (8x(0031abcdefghijklmnopqrstuvwxyz.0006ab.1. */ + 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, + 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, + 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, + 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, + 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, + 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, + 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, /* Bad: */ 0x05, 0x30, 0x30, 0x30, 0x36, 0x61, 0x01, 0x32, 0x00 +}; + +char desc_4[] = "Domain has the maximum allowed length (255)."; + +uint8_t wire_dom_4[] = { /* Good: Domain: (8x)0031abcdefghijklmnopqrstuvwxyz.03.03. */ + 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, + 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, + 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, + 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, + 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, + 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, + 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, /* Good: */ 0x02, 0x30, 0x33, 0x02, 0x30, 0x33, 0x00 +}; + +char desc_5[] = "Domain has a maximum length label (63) in the 255th position."; + +uint8_t wire_dom_5[] = { /* Bad: Domain: (8x)0031abcdefghijklmnopqrstuvwxyz.03.03.65abc...zab...zab...ghi. */ + 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, + 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, + 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, + 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, + 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, + 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, + 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, /* Bad: */ 0x02, 0x30, 0x33, 0x02, 0x30, 0x33, 0x3f, 0x36, + 0x33, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x00 +}; + +char desc_6[] = "Domain has a too long label (65) in the 255th position."; + +uint8_t wire_dom_6[] = { /* Bad: Domain: (8x)0031abcdefghijklmnopqrstuvwxyz.03.03.66abc...zab...zab...ijk. */ + 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, + 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, + 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, + 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, + 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, + 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, + 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, /* Bad: */ 0x02, 0x30, 0x33, 0x02, 0x30, 0x33, 0x41, 0x36, + 0x36, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x00 +}; + +char desc_7[] = "Domain has a too long label (65) in the 187th position."; + +uint8_t wire_dom_7[] = { /* Bad: Domain: (6x)0031abcdefghijklmnopqrstuvwxyz.65abc..zab...zab...ijk. */ + 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, + 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, + 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, + 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, + 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, + 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + /* Bad: */ 0x41, 0x36, + 0x36, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x61, 0x62, 0x63, 0x64, 0x65, + 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x00 +}; + +char desc_8[] = "Domains has the maximum allowed length and ends with a maximum length label."; + +uint8_t wire_dom_8[] = { /* Good: Domain: (6x)0031abcdefghijklmnopqrstuvwxyz.0004.0064abc..zab...zabcdefg. */ + 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, + 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, + 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, + 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, + 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, + 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x03, 0x30, 0x30, 0x34 ,/* Good: */ 0x3f, 0x30, + 0x30, 0x36, 0x34, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x61, 0x62, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x00 +}; + +char desc_9[] = "Domains has 254 octets, one less than the maximum allowed length."; + +uint8_t wire_dom_9[] = { /* Good: Domain: (6x)0031abcdefghijklmnopqrstuvwxyz.0004.0064abc..zab...zabcdef. */ + 0x1e, 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, + 0x30, 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, + 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, + 0x30, 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, + 0x33, 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, + 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, + 0x31, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x1e, 0x30, 0x30, 0x33, 0x31, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x03, 0x30, 0x30, 0x34 ,/* Good: */ 0x3e, 0x30, + 0x30, 0x35, 0x34, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, + 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x61, 0x62, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x00 +}; + + /** Test dname to string with long domain names. */ +static void +test_long_names(void) +{ + /* Set to 1 for verbose output, 0 turns it off. */ + int verbtest = 0; + + uint8_t* wire_doms[] = {wire_dom_1, wire_dom_2, wire_dom_3, + wire_dom_4, wire_dom_5, wire_dom_6, wire_dom_7, wire_dom_8, + wire_dom_9, 0}; + char* descs[] = {desc_1, desc_2, desc_3, desc_4, desc_5, desc_6, + desc_7, desc_8, desc_9, 0}; + + int n; + char string_domain[260]; + uint8_t** wd = wire_doms; + int di = 0; + int skip = 5; /* 0..6 */ + + while (*wd) { + + if(verbtest) + printf("Test: %s\n", descs[di++]); + + memset(string_domain, 0xff, sizeof(string_domain)); + dname_str(*wd, string_domain); + for (n = 0 ; n < (int)sizeof(string_domain); ++n) { + if ((uint8_t)string_domain[n] == 0xff) + break; + } + if(verbtest) + printf("dname_str: L=%d, S=Skipping %d labels...%s\n", + n, skip, string_domain + skip*31); + unit_assert(n <= 255); + + memset(string_domain, 0xff, sizeof(string_domain)); + sldns_wire2str_dname_buf(*wd, + strlen((char*)*wd)+1 /* strlen works with these test strings */, + string_domain, + 255 /* for comparable result to dname_str */ ); + for (n = 0 ; n < (int)sizeof(string_domain); ++n) { + if ((uint8_t)string_domain[n] == 0xff) + break; + } + if(verbtest) + printf("sldns_wire2str_dname_buf: L=%d, S=Skipping %d labels...%s\n", + n, skip, string_domain + skip*31); + unit_assert(n <= 255); + + ++wd; + } +} + static void dname_test_str(sldns_buffer* buff) { @@ -1019,6 +1276,8 @@ dname_test_str(sldns_buffer* buff) unit_assert(0); } } + + test_long_names(); } void dname_test(void) diff --git a/util/data/dname.c b/util/data/dname.c index d29a8e034..fe7188442 100644 --- a/util/data/dname.c +++ b/util/data/dname.c @@ -644,27 +644,29 @@ void dname_str(uint8_t* dname, char* str) if(!dname || !*dname) { *s++ = '.'; *s = 0; - goto out; + return; } lablen = *dname++; while(lablen) { + len += lablen+1; + if(len >= LDNS_MAX_DOMAINLEN) { + if ((s-str) >= (LDNS_MAX_DOMAINLEN-1)) + s = str + LDNS_MAX_DOMAINLEN - 2; + *s++ = '&'; + *s = 0; + return; + } if(lablen > LDNS_MAX_LABELLEN) { *s++ = '#'; *s = 0; - goto out; - } - len += lablen+1; - if(len >= LDNS_MAX_DOMAINLEN) { - *s++ = '&'; - *s = 0; - goto out; + return; } while(lablen--) { if(isalnum((unsigned char)*dname) || *dname == '-' || *dname == '_' || *dname == '*') *s++ = *(char*)dname++; - else { + else { *s++ = '?'; dname++; } @@ -673,10 +675,6 @@ void dname_str(uint8_t* dname, char* str) lablen = *dname++; } *s = 0; - -out: - log_assert(s - str < LDNS_MAX_DOMAINLEN); - return; } int From 3ec5d78ac9a8b38ecaf0cdef83a230e76418330f Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 7 Aug 2025 16:09:47 +0200 Subject: [PATCH 07/23] - Fix that edns-subnet failure to create a subquery errors as servfail, and not formerror. --- doc/Changelog | 2 ++ edns-subnet/subnetmod.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index 14955de93..a3b36d8c7 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,8 @@ 7 August 2025: Wouter - Fix dname_str for printout of long names. Thanks to Jan Komissar for the fix. + - Fix that edns-subnet failure to create a subquery errors as + servfail, and not formerror. 6 August 2025: Wouter - Fix edns subnet, so that the subquery without subnet is stored in diff --git a/edns-subnet/subnetmod.c b/edns-subnet/subnetmod.c index 6f71129d9..88310a785 100644 --- a/edns-subnet/subnetmod.c +++ b/edns-subnet/subnetmod.c @@ -608,7 +608,7 @@ generate_lookup_without_subnet(struct module_qstate *qstate, int id, verbose(VERB_ALGO, "subnetcache: make subquery to look up without subnet"); if(!generate_sub_request(qstate, id, sq)) { verbose(VERB_ALGO, "Could not generate sub query"); - qstate->return_rcode = LDNS_RCODE_FORMERR; + qstate->return_rcode = LDNS_RCODE_SERVFAIL; qstate->return_msg = NULL; return module_finished; } From 752a3f7f521fbc1efcf4de602bcf1fd80dbbf41b Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 7 Aug 2025 16:19:10 +0200 Subject: [PATCH 08/23] - Fix to whitespace in dname_str. --- doc/Changelog | 1 + util/data/dname.c | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index a3b36d8c7..2cbd25db3 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -3,6 +3,7 @@ for the fix. - Fix that edns-subnet failure to create a subquery errors as servfail, and not formerror. + - Fix to whitespace in dname_str. 6 August 2025: Wouter - Fix edns subnet, so that the subquery without subnet is stored in diff --git a/util/data/dname.c b/util/data/dname.c index fe7188442..5370aa6f9 100644 --- a/util/data/dname.c +++ b/util/data/dname.c @@ -650,11 +650,11 @@ void dname_str(uint8_t* dname, char* str) while(lablen) { len += lablen+1; if(len >= LDNS_MAX_DOMAINLEN) { - if ((s-str) >= (LDNS_MAX_DOMAINLEN-1)) - s = str + LDNS_MAX_DOMAINLEN - 2; - *s++ = '&'; - *s = 0; - return; + if ((s-str) >= (LDNS_MAX_DOMAINLEN-1)) + s = str + LDNS_MAX_DOMAINLEN - 2; + *s++ = '&'; + *s = 0; + return; } if(lablen > LDNS_MAX_LABELLEN) { *s++ = '#'; @@ -666,7 +666,7 @@ void dname_str(uint8_t* dname, char* str) || *dname == '-' || *dname == '_' || *dname == '*') *s++ = *(char*)dname++; - else { + else { *s++ = '?'; dname++; } From 19b289feec00c7431840e304fd8a752ab3473008 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 12 Aug 2025 11:59:48 +0200 Subject: [PATCH 09/23] - Fix that unbound-control dump_cache releases the cache locks every so often, so that the server stays responsive. --- daemon/cachedump.c | 180 ++++++++++++++++++++++++++++++++------------- 1 file changed, 130 insertions(+), 50 deletions(-) diff --git a/daemon/cachedump.c b/daemon/cachedump.c index ba986c763..7c0aa68a2 100644 --- a/daemon/cachedump.c +++ b/daemon/cachedump.c @@ -99,41 +99,129 @@ dump_rrset(RES* ssl, struct ub_packed_rrset_key* k, return 1; } -/** dump lruhash rrset cache */ +/** dump lruhash cache and call callback for every item. */ static int -dump_rrset_lruhash(RES* ssl, struct lruhash* h, time_t now) +dump_lruhash(struct lruhash* table, int (*func)(void*, void*), void* arg) { - struct lruhash_entry* e; - /* lruhash already locked by caller */ - /* walk in order of lru; best first */ - for(e=h->lru_start; e; e = e->lru_next) { - lock_rw_rdlock(&e->lock); - if(!dump_rrset(ssl, (struct ub_packed_rrset_key*)e->key, - (struct packed_rrset_data*)e->data, now)) { - lock_rw_unlock(&e->lock); - return 0; + int just_started = 1; + int not_done = 1; + hashvalue_type hash; + size_t num = 0; /* number of entries processed. */ + size_t max = 1000; /* number of entries after which it unlocks. */ + + while(not_done) { + size_t i; /* hash bin. */ + /* Process a number of items. */ + num = 0; + lock_quick_lock(&table->lock); + if(just_started) { + i = 0; + } else { + i = hash&table->size_mask; } - lock_rw_unlock(&e->lock); + while(num < max) { + /* Process bin. */ + int found = 0; + size_t num_bin = 0; + struct lruhash_bin* bin = &table->array[i]; + struct lruhash_entry* e; + lock_quick_lock(&bin->lock); + for(e = bin->overflow_list; e; e = e->overflow_next) { + /* Entry e is locked by the func. */ + if(!(*func)(e, arg)) { + lock_quick_unlock(&bin->lock); + lock_quick_unlock(&table->lock); + return 0; + } + num_bin++; + } + lock_quick_unlock(&bin->lock); + /* This addition of bin number of entries may take + * it over the max. */ + num += num_bin; + + /* Move to next bin. */ + /* Find one with an entry, with a hash value, so we + * can continue from the hash value. The hash value + * can be indexed also if the array changes size. */ + i++; + while(i < table->size) { + bin = &table->array[i]; + lock_quick_lock(&bin->lock); + if(bin->overflow_list) { + hash = bin->overflow_list->hash; + lock_quick_unlock(&bin->lock); + found = 1; + just_started = 0; + break; + } + lock_quick_unlock(&bin->lock); + i++; + } + if(!found) { + not_done = 0; + lock_quick_unlock(&table->lock); + break; + } + } + lock_quick_unlock(&table->lock); } return 1; } +/** dump slabhash cache and call callback for every item. */ +static int +dump_slabhash(struct slabhash* sh, int (*func)(void*, void*), void* arg) +{ + /* Process a number of items at a time, then unlock the cache, + * so that ordinary processing can continue. Keep an iteration marker + * to continue the loop. That means the cache can change, items + * could be inserted and deleted. And, for example, the hash table + * can grow. */ + size_t slab; + for(slab=0; slabsize; slab++) { + if(!dump_lruhash(sh->array[slab], func, arg)) + return 0; + } + return 1; +} + +/** Struct for dump information. */ +struct dump_info { + /** The worker. */ + struct worker* worker; + /** The printout connection. */ + RES* ssl; +}; + +/** Dump the rrset cache entry */ +static int +dump_rrset_entry(void* entryp, void* arg) +{ + struct dump_info* dump_info = (struct dump_info*)arg; + struct lruhash_entry* e = (struct lruhash_entry*)entryp; + lock_rw_rdlock(&e->lock); + if(!dump_rrset(dump_info->ssl, (struct ub_packed_rrset_key*)e->key, + (struct packed_rrset_data*)e->data, + *dump_info->worker->env.now)) { + lock_rw_unlock(&e->lock); + return 0; + } + lock_rw_unlock(&e->lock); + return 1; +} + /** dump rrset cache */ static int dump_rrset_cache(RES* ssl, struct worker* worker) { struct rrset_cache* r = worker->env.rrset_cache; - size_t slab; + struct dump_info dump_info; + dump_info.worker = worker; + dump_info.ssl = ssl; if(!ssl_printf(ssl, "START_RRSET_CACHE\n")) return 0; - for(slab=0; slabtable.size; slab++) { - lock_quick_lock(&r->table.array[slab]->lock); - if(!dump_rrset_lruhash(ssl, r->table.array[slab], - *worker->env.now)) { - lock_quick_unlock(&r->table.array[slab]->lock); - return 0; - } - lock_quick_unlock(&r->table.array[slab]->lock); - } + if(!dump_slabhash(&r->table, &dump_rrset_entry, &dump_info)) + return 0; return ssl_printf(ssl, "END_RRSET_CACHE\n"); } @@ -247,30 +335,27 @@ copy_msg(struct regional* region, struct lruhash_entry* e, return (*k)->qname != NULL; } -/** dump lruhash msg cache */ +/** Dump the msg entry. */ static int -dump_msg_lruhash(RES* ssl, struct worker* worker, struct lruhash* h) +dump_msg_entry(void* entryp, void* arg) { - struct lruhash_entry* e; + struct dump_info* dump_info = (struct dump_info*)arg; + struct lruhash_entry* e = (struct lruhash_entry*)entryp; struct query_info* k; struct reply_info* d; - /* lruhash already locked by caller */ - /* walk in order of lru; best first */ - for(e=h->lru_start; e; e = e->lru_next) { - regional_free_all(worker->scratchpad); - lock_rw_rdlock(&e->lock); - /* make copy of rrset in worker buffer */ - if(!copy_msg(worker->scratchpad, e, &k, &d)) { - lock_rw_unlock(&e->lock); - return 0; - } + regional_free_all(dump_info->worker->scratchpad); + /* Make copy of rrset in worker buffer. */ + lock_rw_rdlock(&e->lock); + if(!copy_msg(dump_info->worker->scratchpad, e, &k, &d)) { lock_rw_unlock(&e->lock); - /* release lock so we can lookup the rrset references - * in the rrset cache */ - if(!dump_msg(ssl, k, d, *worker->env.now)) { - return 0; - } + return 0; + } + lock_rw_unlock(&e->lock); + /* Release lock so we can lookup the rrset references + * in the rrset cache. */ + if(!dump_msg(dump_info->ssl, k, d, *dump_info->worker->env.now)) { + return 0; } return 1; } @@ -279,17 +364,12 @@ dump_msg_lruhash(RES* ssl, struct worker* worker, struct lruhash* h) static int dump_msg_cache(RES* ssl, struct worker* worker) { - struct slabhash* sh = worker->env.msg_cache; - size_t slab; + struct dump_info dump_info; + dump_info.worker = worker; + dump_info.ssl = ssl; if(!ssl_printf(ssl, "START_MSG_CACHE\n")) return 0; - for(slab=0; slabsize; slab++) { - lock_quick_lock(&sh->array[slab]->lock); - if(!dump_msg_lruhash(ssl, worker, sh->array[slab])) { - lock_quick_unlock(&sh->array[slab]->lock); - return 0; - } - lock_quick_unlock(&sh->array[slab]->lock); - } + if(!dump_slabhash(worker->env.msg_cache, &dump_msg_entry, &dump_info)) + return 0; return ssl_printf(ssl, "END_MSG_CACHE\n"); } From d55f20fdcc5bf3a92b0ea4e24328a434bb74bbc1 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 12 Aug 2025 12:00:01 +0200 Subject: [PATCH 10/23] - Fix that unbound-control dump_cache releases the cache locks every so often, so that the server stays responsive. Changelog entry for it. --- doc/Changelog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 2cbd25db3..b0b6cc978 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +12 August 2025: Wouter + - Fix that unbound-control dump_cache releases the cache locks + every so often, so that the server stays responsive. + 7 August 2025: Wouter - Fix dname_str for printout of long names. Thanks to Jan Komissar for the fix. From fad747308f703efdda356c6c8a1d426bb2430ef9 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 13 Aug 2025 11:31:42 +0200 Subject: [PATCH 11/23] - unbound-control cache_lookup prints the cached rrsets and messages for those. --- daemon/remote.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/daemon/remote.c b/daemon/remote.c index c17254bb5..4cb26f938 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -1742,6 +1742,157 @@ do_view_datas_remove(struct daemon_remote* rc, RES* ssl, struct worker* worker, (void)ssl_printf(ssl, "removed %d datas\n", num); } +/** information for the domain search */ +struct cache_lookup_info { + /** The connection to print on. */ + RES* ssl; + /** The worker. */ + struct worker* worker; + /** The domain, in wireformat. */ + uint8_t* nm; + /** The length of nm. */ + size_t nmlen; +}; + +static void +cache_lookup_rrset(struct lruhash_entry* e, void* arg) +{ + struct cache_lookup_info* inf = (struct cache_lookup_info*)arg; + struct ub_packed_rrset_key* k = (struct ub_packed_rrset_key*)e->key; + struct packed_rrset_data* d = (struct packed_rrset_data*)e->data; + char bla[255], bla2[255]; + dname_str(k->rk.dname, bla); + dname_str(inf->nm, bla2); + if(*inf->worker->env.now < d->ttl && + k->id != 0 && /* not deleted */ + dname_subdomain_c(k->rk.dname, inf->nm)) { + size_t i; + for(i=0; icount + d->rrsig_count; i++) { + char s[65535]; + if(!packed_rr_to_string(k, i, *inf->worker->env.now, + s, sizeof(s))) { + ssl_printf(inf->ssl, "BADRR\n"); + return; + } + ssl_printf(inf->ssl, "%s", s); + } + ssl_printf(inf->ssl, "\n"); + } +} + +static void +cache_lookup_msg(struct lruhash_entry* e, void* arg) +{ + struct cache_lookup_info* inf = (struct cache_lookup_info*)arg; + struct msgreply_entry* k = (struct msgreply_entry*)e->key; + struct reply_info* d = (struct reply_info*)e->data; + if(*inf->worker->env.now < d->ttl && + dname_subdomain_c(k->key.qname, inf->nm)) { + size_t i; + char s[65535], tp[32], cl[32], rc[32], fg[32]; + sldns_wire2str_dname_buf(k->key.qname, k->key.qname_len, + s, sizeof(s)); + sldns_wire2str_type_buf(k->key.qtype, tp, sizeof(tp)); + sldns_wire2str_class_buf(k->key.qclass, cl, sizeof(cl)); + sldns_wire2str_rcode_buf(FLAGS_GET_RCODE(d->flags), + rc, sizeof(rc)); + snprintf(fg, sizeof(fg), "%s%s%s%s%s%s%s%s", + ((d->flags&BIT_QR)?" QR":""), + ((d->flags&BIT_AA)?" AA":""), + ((d->flags&BIT_TC)?" TC":""), + ((d->flags&BIT_RD)?" RD":""), + ((d->flags&BIT_RA)?" RA":""), + ((d->flags&BIT_Z)?" Z":""), + ((d->flags&BIT_AD)?" AD":""), + ((d->flags&BIT_CD)?" CD":"")); + if(!rrset_array_lock(d->ref, d->rrset_count, + *inf->worker->env.now)) { + /* rrsets have timed out or do not exist */ + return; + } + ssl_printf(inf->ssl, + "msg %s %s %s%s %s %d %d " ARG_LL "d %d %u %u %u %d %s\n", + s, cl, tp, fg, rc, + (int)d->flags, (int)d->qdcount, + (long long)(d->ttl-*inf->worker->env.now), + (int)d->security, + (unsigned)d->an_numrrsets, + (unsigned)d->ns_numrrsets, + (unsigned)d->ar_numrrsets, + (int)d->reason_bogus, + d->reason_bogus_str?d->reason_bogus_str:""); + for(i=0; irrset_count; i++) { + struct ub_packed_rrset_key* rk = d->rrsets[i]; + struct packed_rrset_data* rd = (struct packed_rrset_data*)rk->entry.data; + size_t j; + for(j=0; jcount + rd->rrsig_count; j++) { + if(!packed_rr_to_string(rk, j, + *inf->worker->env.now, s, sizeof(s))) { + ssl_printf(inf->ssl, "BADRR\n"); + return; + } + ssl_printf(inf->ssl, "%s", s); + } + } + rrset_array_unlock(d->ref, d->rrset_count); + ssl_printf(inf->ssl, "\n"); + } +} + +/** perform cache search for domain */ +static void +do_cache_lookup_domain(RES* ssl, struct worker* worker, uint8_t* nm, + size_t nmlen) +{ + struct cache_lookup_info inf; + inf.ssl = ssl; + inf.worker = worker; + inf.nm = nm; + inf.nmlen = nmlen; + slabhash_traverse(&worker->env.rrset_cache->table, 0, + &cache_lookup_rrset, &inf); + slabhash_traverse(worker->env.msg_cache, 0, &cache_lookup_msg, &inf); +} + +/** cache lookup of domain */ +static void +do_cache_lookup(RES* ssl, struct worker* worker, char* arg) +{ + uint8_t nm[LDNS_MAX_DOMAINLEN+1]; + size_t nmlen; + int status; + char* s = arg, *next = NULL; + + /* Find the commandline arguments of domains. */ + while(s && *s != 0) { + s = skipwhite(s); + if(*s == 0) + break; + if(strchr(s, ' ') || strchr(s, '\t')) { + char* sp = strchr(s, ' '); + if(strchr(s, '\t') != 0 && strchr(s, '\t') < sp) + sp = strchr(s, '\t'); + *sp = 0; + next = sp+1; + } else { + next = NULL; + } + + nmlen = sizeof(nm); + status = sldns_str2wire_dname_buf(s, nm, &nmlen); + if(status != 0) { + ssl_printf(ssl, "error cannot parse name %s at %d: %s\n", s, + LDNS_WIREPARSE_OFFSET(status), + sldns_get_errorstr_parse(status)); + return; + } + + do_cache_lookup_domain(ssl, worker, nm, nmlen); + + s = next; + } +} + /** cache lookup of nameservers */ static void do_lookup(RES* ssl, struct worker* worker, char* arg) @@ -3615,6 +3766,9 @@ execute_cmd(struct daemon_remote* rc, struct rc_state* s, RES* ssl, char* cmd, if(rc) distribute_cmd(rc, ssl, cmd); do_flush_requestlist(ssl, worker); return; + } else if(cmdcmp(p, "cache_lookup", 12)) { + do_cache_lookup(ssl, worker, skipwhite(p+12)); + return; } else if(cmdcmp(p, "lookup", 6)) { do_lookup(ssl, worker, skipwhite(p+6)); return; From 2f7890eb6e5162a48863a7b8a941fe243aa7c1ed Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 13 Aug 2025 11:36:47 +0200 Subject: [PATCH 12/23] - unbound-control cache_lookup prints the cached rrsets and messages for those. Changelog and information. --- doc/Changelog | 4 ++++ doc/unbound-control.rst | 6 ++++++ smallapp/unbound-control.c | 1 + 3 files changed, 11 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index b0b6cc978..8d0e4cb2d 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +13 August 2025: Wouter + - unbound-control cache_lookup prints the cached rrsets + and messages for those. + 12 August 2025: Wouter - Fix that unbound-control dump_cache releases the cache locks every so often, so that the server stays responsive. diff --git a/doc/unbound-control.rst b/doc/unbound-control.rst index 1f3cc4a8e..a42809e66 100644 --- a/doc/unbound-control.rst +++ b/doc/unbound-control.rst @@ -386,6 +386,12 @@ There are several commands that the server understands. Not supported in remote Unbounds in multi-process operation. +@@UAHL@unbound-control.commands@cache_lookup@@ *names* + Print to stdout the RRsets and messages that are in the cache. + For every name listed the content at or under the name is printed. + Several names separated by spaces can be given, each is printed. + + @@UAHL@unbound-control.commands@lookup@@ *name* Print to stdout the name servers that would be used to look up the name specified. diff --git a/smallapp/unbound-control.c b/smallapp/unbound-control.c index 994a42870..c58704c82 100644 --- a/smallapp/unbound-control.c +++ b/smallapp/unbound-control.c @@ -143,6 +143,7 @@ usage(void) printf(" load_cache load cache from stdin\n"); printf(" (not supported in remote unbounds in\n"); printf(" multi-process operation)\n"); + printf(" cache_lookup print rrsets and msgs at or under the names\n"); printf(" lookup print nameservers for name\n"); printf(" flush [+c] flushes common types for name from cache\n"); printf(" types: A, AAAA, MX, PTR, NS,\n"); From 651a71fa76d0c916749f58bbfbd8535259b0bacc Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 13 Aug 2025 11:59:53 +0200 Subject: [PATCH 13/23] - Fix to remove debug from cache_lookup. --- daemon/remote.c | 3 --- doc/Changelog | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 4cb26f938..0b1cd64c5 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -1760,9 +1760,6 @@ cache_lookup_rrset(struct lruhash_entry* e, void* arg) struct cache_lookup_info* inf = (struct cache_lookup_info*)arg; struct ub_packed_rrset_key* k = (struct ub_packed_rrset_key*)e->key; struct packed_rrset_data* d = (struct packed_rrset_data*)e->data; - char bla[255], bla2[255]; - dname_str(k->rk.dname, bla); - dname_str(inf->nm, bla2); if(*inf->worker->env.now < d->ttl && k->id != 0 && /* not deleted */ dname_subdomain_c(k->rk.dname, inf->nm)) { diff --git a/doc/Changelog b/doc/Changelog index 8d0e4cb2d..0ae4873ef 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,7 @@ 13 August 2025: Wouter - unbound-control cache_lookup prints the cached rrsets and messages for those. + - Fix to remove debug from cache_lookup. 12 August 2025: Wouter - Fix that unbound-control dump_cache releases the cache locks From d122ae64906e188f1dd436c5c1ddced18d486922 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 13 Aug 2025 12:02:41 +0200 Subject: [PATCH 14/23] - Fix to unlock cache_lookup message for malformed records. --- daemon/remote.c | 1 + doc/Changelog | 1 + 2 files changed, 2 insertions(+) diff --git a/daemon/remote.c b/daemon/remote.c index 0b1cd64c5..20221d64e 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -1825,6 +1825,7 @@ cache_lookup_msg(struct lruhash_entry* e, void* arg) for(j=0; jcount + rd->rrsig_count; j++) { if(!packed_rr_to_string(rk, j, *inf->worker->env.now, s, sizeof(s))) { + rrset_array_unlock(d->ref, d->rrset_count); ssl_printf(inf->ssl, "BADRR\n"); return; } diff --git a/doc/Changelog b/doc/Changelog index 0ae4873ef..7000b1649 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,7 @@ - unbound-control cache_lookup prints the cached rrsets and messages for those. - Fix to remove debug from cache_lookup. + - Fix to unlock cache_lookup message for malformed records. 12 August 2025: Wouter - Fix that unbound-control dump_cache releases the cache locks From 4f790bd65ed30a62b895951ad7be972ee80dbb3b Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 14 Aug 2025 11:25:40 +0200 Subject: [PATCH 15/23] - Fix to increase responsiveness of dump_cache. --- daemon/cachedump.c | 2 +- doc/Changelog | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/daemon/cachedump.c b/daemon/cachedump.c index 7c0aa68a2..daa4e0ab1 100644 --- a/daemon/cachedump.c +++ b/daemon/cachedump.c @@ -107,7 +107,7 @@ dump_lruhash(struct lruhash* table, int (*func)(void*, void*), void* arg) int not_done = 1; hashvalue_type hash; size_t num = 0; /* number of entries processed. */ - size_t max = 1000; /* number of entries after which it unlocks. */ + size_t max = 2; /* number of entries after which it unlocks. */ while(not_done) { size_t i; /* hash bin. */ diff --git a/doc/Changelog b/doc/Changelog index 7000b1649..701379537 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +14 August 2025: Wouter + - Fix to increase responsiveness of dump_cache. + 13 August 2025: Wouter - unbound-control cache_lookup prints the cached rrsets and messages for those. From 991108af8d73d650d79ffebe33eac840e8bd8ad2 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 14 Aug 2025 12:20:10 +0200 Subject: [PATCH 16/23] - Fix to decouple file descriptor activity and cache lookups in dump_cache. --- daemon/cachedump.c | 212 ++++++++++++++++++++++++++++----------------- 1 file changed, 131 insertions(+), 81 deletions(-) diff --git a/daemon/cachedump.c b/daemon/cachedump.c index daa4e0ab1..941cb9dfc 100644 --- a/daemon/cachedump.c +++ b/daemon/cachedump.c @@ -62,38 +62,81 @@ #include "sldns/wire2str.h" #include "sldns/str2wire.h" +static void spool_txt_printf(struct config_strlist_head* txt, + const char* format, ...) ATTR_FORMAT(printf, 2, 3); + +/** Append to strlist at end, and log error if out of memory. */ +static void +spool_txt_string(struct config_strlist_head* txt, char* str) +{ + if(!cfg_strlist_append(txt, strdup(str))) { + log_err("out of memory in spool text"); + } +} + +/** Spool txt to spool list. */ +static void +spool_txt_vmsg(struct config_strlist_head* txt, const char* format, + va_list args) +{ + char msg[65535]; + vsnprintf(msg, sizeof(msg), format, args); + spool_txt_string(txt, msg); +} + +/** Print item to spool list. On alloc failure the list is as before. */ +static void +spool_txt_printf(struct config_strlist_head* txt, const char* format, ...) +{ + va_list args; + va_start(args, format); + spool_txt_vmsg(txt, format, args); + va_end(args); +} + /** dump one rrset zonefile line */ -static int -dump_rrset_line(RES* ssl, struct ub_packed_rrset_key* k, time_t now, size_t i) +static void +dump_rrset_line(struct config_strlist_head* txt, struct ub_packed_rrset_key* k, + time_t now, size_t i) { char s[65535]; if(!packed_rr_to_string(k, i, now, s, sizeof(s))) { - return ssl_printf(ssl, "BADRR\n"); + spool_txt_string(txt, "BADRR\n"); + return; } - return ssl_printf(ssl, "%s", s); + spool_txt_string(txt, s); } /** dump rrset key and data info */ -static int -dump_rrset(RES* ssl, struct ub_packed_rrset_key* k, +static void +dump_rrset(struct config_strlist_head* txt, struct ub_packed_rrset_key* k, struct packed_rrset_data* d, time_t now) { size_t i; /* rd lock held by caller */ - if(!k || !d) return 1; - if(k->id == 0) return 1; /* deleted */ - if(d->ttl < now) return 1; /* expired */ + if(!k || !d) return; + if(k->id == 0) return; /* deleted */ + if(d->ttl < now) return; /* expired */ /* meta line */ - if(!ssl_printf(ssl, ";rrset%s " ARG_LL "d %u %u %d %d\n", + spool_txt_printf(txt, ";rrset%s " ARG_LL "d %u %u %d %d\n", (k->rk.flags & PACKED_RRSET_NSEC_AT_APEX)?" nsec_apex":"", (long long)(d->ttl - now), (unsigned)d->count, (unsigned)d->rrsig_count, (int)d->trust, (int)d->security - )) - return 0; + ); for(i=0; icount + d->rrsig_count; i++) { - if(!dump_rrset_line(ssl, k, now, i)) + dump_rrset_line(txt, k, now, i); + } +} + +/** Spool strlist to the output. */ +static int +spool_strlist(RES* ssl, struct config_strlist* list) +{ + struct config_strlist* s; + for(s=list; s; s=s->next) { + if(!ssl_printf(ssl, "%s", s->str)) return 0; } return 1; @@ -101,13 +144,17 @@ dump_rrset(RES* ssl, struct ub_packed_rrset_key* k, /** dump lruhash cache and call callback for every item. */ static int -dump_lruhash(struct lruhash* table, int (*func)(void*, void*), void* arg) +dump_lruhash(struct lruhash* table, + void (*func)(struct lruhash_entry*, struct config_strlist_head*, void*), + RES* ssl, void* arg) { int just_started = 1; int not_done = 1; hashvalue_type hash; size_t num = 0; /* number of entries processed. */ size_t max = 2; /* number of entries after which it unlocks. */ + struct config_strlist_head txt; /* Text strings spooled. */ + memset(&txt, 0, sizeof(txt)); while(not_done) { size_t i; /* hash bin. */ @@ -128,11 +175,7 @@ dump_lruhash(struct lruhash* table, int (*func)(void*, void*), void* arg) lock_quick_lock(&bin->lock); for(e = bin->overflow_list; e; e = e->overflow_next) { /* Entry e is locked by the func. */ - if(!(*func)(e, arg)) { - lock_quick_unlock(&bin->lock); - lock_quick_unlock(&table->lock); - return 0; - } + func(e, &txt, arg); num_bin++; } lock_quick_unlock(&bin->lock); @@ -165,13 +208,34 @@ dump_lruhash(struct lruhash* table, int (*func)(void*, void*), void* arg) } } lock_quick_unlock(&table->lock); + /* Print the spooled items, that are collected while the + * locks are locked. The print happens while they are not + * locked. */ + if(txt.first) { + if(!spool_strlist(ssl, txt.first)) { + config_delstrlist(txt.first); + return 0; + } + config_delstrlist(txt.first); + memset(&txt, 0, sizeof(txt)); + } + } + /* Print the final spooled items. */ + if(txt.first) { + if(!spool_strlist(ssl, txt.first)) { + config_delstrlist(txt.first); + return 0; + } + config_delstrlist(txt.first); } return 1; } /** dump slabhash cache and call callback for every item. */ static int -dump_slabhash(struct slabhash* sh, int (*func)(void*, void*), void* arg) +dump_slabhash(struct slabhash* sh, + void (*func)(struct lruhash_entry*, struct config_strlist_head*, void*), + RES* ssl, void* arg) { /* Process a number of items at a time, then unlock the cache, * so that ordinary processing can continue. Keep an iteration marker @@ -180,7 +244,7 @@ dump_slabhash(struct slabhash* sh, int (*func)(void*, void*), void* arg) * can grow. */ size_t slab; for(slab=0; slabsize; slab++) { - if(!dump_lruhash(sh->array[slab], func, arg)) + if(!dump_lruhash(sh->array[slab], func, ssl, arg)) return 0; } return 1; @@ -195,20 +259,16 @@ struct dump_info { }; /** Dump the rrset cache entry */ -static int -dump_rrset_entry(void* entryp, void* arg) +static void +dump_rrset_entry(struct lruhash_entry* e, struct config_strlist_head* txt, + void* arg) { struct dump_info* dump_info = (struct dump_info*)arg; - struct lruhash_entry* e = (struct lruhash_entry*)entryp; lock_rw_rdlock(&e->lock); - if(!dump_rrset(dump_info->ssl, (struct ub_packed_rrset_key*)e->key, + dump_rrset(txt, (struct ub_packed_rrset_key*)e->key, (struct packed_rrset_data*)e->data, - *dump_info->worker->env.now)) { - lock_rw_unlock(&e->lock); - return 0; - } + *dump_info->worker->env.now); lock_rw_unlock(&e->lock); - return 1; } /** dump rrset cache */ @@ -220,14 +280,14 @@ dump_rrset_cache(RES* ssl, struct worker* worker) dump_info.worker = worker; dump_info.ssl = ssl; if(!ssl_printf(ssl, "START_RRSET_CACHE\n")) return 0; - if(!dump_slabhash(&r->table, &dump_rrset_entry, &dump_info)) + if(!dump_slabhash(&r->table, &dump_rrset_entry, ssl, &dump_info)) return 0; return ssl_printf(ssl, "END_RRSET_CACHE\n"); } /** dump message to rrset reference */ -static int -dump_msg_ref(RES* ssl, struct ub_packed_rrset_key* k) +static void +dump_msg_ref(struct config_strlist_head* txt, struct ub_packed_rrset_key* k) { char* nm, *tp, *cl; nm = sldns_wire2str_dname(k->rk.dname, k->rk.dname_len); @@ -237,30 +297,25 @@ dump_msg_ref(RES* ssl, struct ub_packed_rrset_key* k) free(nm); free(tp); free(cl); - return ssl_printf(ssl, "BADREF\n"); - } - if(!ssl_printf(ssl, "%s %s %s %d\n", nm, cl, tp, (int)k->rk.flags)) { - free(nm); - free(tp); - free(cl); - return 0; + spool_txt_string(txt, "BADREF\n"); + return; } + spool_txt_printf(txt, "%s %s %s %d\n", nm, cl, tp, (int)k->rk.flags); free(nm); free(tp); free(cl); - - return 1; } /** dump message entry */ -static int -dump_msg(RES* ssl, struct query_info* k, struct reply_info* d, time_t now) +static void +dump_msg(struct config_strlist_head* txt, struct query_info* k, + struct reply_info* d, time_t now) { size_t i; char* nm, *tp, *cl; - if(!k || !d) return 1; - if(d->ttl < now) return 1; /* expired */ - + if(!k || !d) return; + if(d->ttl < now) return; /* expired */ + nm = sldns_wire2str_dname(k->qname, k->qname_len); tp = sldns_wire2str_type(k->qtype); cl = sldns_wire2str_class(k->qclass); @@ -268,45 +323,35 @@ dump_msg(RES* ssl, struct query_info* k, struct reply_info* d, time_t now) free(nm); free(tp); free(cl); - return 1; /* skip this entry */ + return; /* skip this entry */ } if(!rrset_array_lock(d->ref, d->rrset_count, now)) { /* rrsets have timed out or do not exist */ free(nm); free(tp); free(cl); - return 1; /* skip this entry */ + return; /* skip this entry */ } - + /* meta line */ - if(!ssl_printf(ssl, "msg %s %s %s %d %d " ARG_LL "d %d %u %u %u %d %s\n", - nm, cl, tp, - (int)d->flags, (int)d->qdcount, - (long long)(d->ttl-now), (int)d->security, - (unsigned)d->an_numrrsets, - (unsigned)d->ns_numrrsets, - (unsigned)d->ar_numrrsets, - (int)d->reason_bogus, - d->reason_bogus_str?d->reason_bogus_str:"")) { - free(nm); - free(tp); - free(cl); - rrset_array_unlock(d->ref, d->rrset_count); - return 0; - } + spool_txt_printf(txt, + "msg %s %s %s %d %d " ARG_LL "d %d %u %u %u %d %s\n", + nm, cl, tp, + (int)d->flags, (int)d->qdcount, + (long long)(d->ttl-now), (int)d->security, + (unsigned)d->an_numrrsets, + (unsigned)d->ns_numrrsets, + (unsigned)d->ar_numrrsets, + (int)d->reason_bogus, + d->reason_bogus_str?d->reason_bogus_str:""); free(nm); free(tp); free(cl); for(i=0; irrset_count; i++) { - if(!dump_msg_ref(ssl, d->rrsets[i])) { - rrset_array_unlock(d->ref, d->rrset_count); - return 0; - } + dump_msg_ref(txt, d->rrsets[i]); } rrset_array_unlock(d->ref, d->rrset_count); - - return 1; } /** copy msg to worker pad */ @@ -336,11 +381,11 @@ copy_msg(struct regional* region, struct lruhash_entry* e, } /** Dump the msg entry. */ -static int -dump_msg_entry(void* entryp, void* arg) +static void +dump_msg_entry(struct lruhash_entry* e, struct config_strlist_head* txt, + void* arg) { struct dump_info* dump_info = (struct dump_info*)arg; - struct lruhash_entry* e = (struct lruhash_entry*)entryp; struct query_info* k; struct reply_info* d; @@ -349,15 +394,13 @@ dump_msg_entry(void* entryp, void* arg) lock_rw_rdlock(&e->lock); if(!copy_msg(dump_info->worker->scratchpad, e, &k, &d)) { lock_rw_unlock(&e->lock); - return 0; + log_err("out of memory in dump_msg_entry"); + return; } lock_rw_unlock(&e->lock); /* Release lock so we can lookup the rrset references * in the rrset cache. */ - if(!dump_msg(dump_info->ssl, k, d, *dump_info->worker->env.now)) { - return 0; - } - return 1; + dump_msg(txt, k, d, *dump_info->worker->env.now); } /** dump msg cache */ @@ -368,7 +411,8 @@ dump_msg_cache(RES* ssl, struct worker* worker) dump_info.worker = worker; dump_info.ssl = ssl; if(!ssl_printf(ssl, "START_MSG_CACHE\n")) return 0; - if(!dump_slabhash(worker->env.msg_cache, &dump_msg_entry, &dump_info)) + if(!dump_slabhash(worker->env.msg_cache, &dump_msg_entry, ssl, + &dump_info)) return 0; return ssl_printf(ssl, "END_MSG_CACHE\n"); } @@ -891,12 +935,18 @@ print_dp_main(RES* ssl, struct delegpt* dp, struct dns_msg* msg) struct ub_packed_rrset_key* k = msg->rep->rrsets[i]; struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; + struct config_strlist_head txt; + memset(&txt, 0, sizeof(txt)); if(d->security == sec_status_bogus) { if(!ssl_printf(ssl, "Address is BOGUS:\n")) return; } - if(!dump_rrset(ssl, k, d, 0)) + dump_rrset(&txt, k, d, 0); + if(!spool_strlist(ssl, txt.first)) { + config_delstrlist(txt.first); return; + } + config_delstrlist(txt.first); } delegpt_count_ns(dp, &n_ns, &n_miss); delegpt_count_addr(dp, &n_addr, &n_res, &n_avail); From 40877f46e59b651e7ea3c40ddccb96bc18bd90d1 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 14 Aug 2025 12:20:22 +0200 Subject: [PATCH 17/23] - Fix to decouple file descriptor activity and cache lookups in dump_cache. Changelog note. --- doc/Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 701379537..24a218f29 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,5 +1,7 @@ 14 August 2025: Wouter - Fix to increase responsiveness of dump_cache. + - Fix to decouple file descriptor activity and cache lookups in + dump_cache. 13 August 2025: Wouter - unbound-control cache_lookup prints the cached rrsets From 81345fe1e3631413b3ce1bf09825b37b7a1018ef Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 15 Aug 2025 13:03:00 +0200 Subject: [PATCH 18/23] - unbound-control cache_lookup +t allows tld and root names. And subnet cache contents are printed. --- daemon/remote.c | 180 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/daemon/remote.c b/daemon/remote.c index 20221d64e..549979990 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -101,6 +101,10 @@ #ifdef USE_CACHEDB #include "cachedb/cachedb.h" #endif +#ifdef CLIENT_SUBNET +#include "edns-subnet/subnetmod.h" +#include "edns-subnet/addrtree.h" +#endif #ifdef HAVE_SYS_TYPES_H # include @@ -1754,6 +1758,156 @@ struct cache_lookup_info { size_t nmlen; }; +#ifdef CLIENT_SUBNET +static void addrtree_traverse_visit_node(struct addrnode* n, addrkey_t* addr, + size_t addr_size, int is_ipv6, time_t now, struct query_info* q, + void (*func)(struct query_info*, struct reply_info*, addrkey_t*, + size_t, int, addrlen_t, int, time_t, void*), void* arg); + +/** Lookup in subnet addrtree */ +static void +cache_lookup_subnet_addrnode(struct query_info* q, struct reply_info* d, + addrkey_t* addr, size_t addr_size, int is_ipv6, addrlen_t scope, + int only_match_scope_zero, time_t ttl, void* arg) +{ + size_t i; + char s[65535], tp[32], cl[32], rc[32], fg[32], astr[64]; + struct cache_lookup_info* inf = (struct cache_lookup_info*)arg; + if(is_ipv6) { + if(addr_size < 16 || inet_ntop(AF_INET6, addr, astr, + sizeof(astr)) == NULL) + snprintf(astr, sizeof(astr), "(inet6ntoperror)"); + } else { + if(addr_size < 4 || inet_ntop(AF_INET, addr, astr, + sizeof(astr)) == NULL) + snprintf(astr, sizeof(astr), "(inetntoperror)"); + } + sldns_wire2str_dname_buf(q->qname, q->qname_len, s, sizeof(s)); + sldns_wire2str_type_buf(q->qtype, tp, sizeof(tp)); + sldns_wire2str_class_buf(q->qclass, cl, sizeof(cl)); + if(!ssl_printf(inf->ssl, "subnet %s/%d%s %s %s %s " ARG_LL "d\n", astr, + (int)scope, (only_match_scope_zero?" scope_zero":""), + s, cl, tp, (long long)(ttl-*inf->worker->env.now))) + return; + sldns_wire2str_rcode_buf(FLAGS_GET_RCODE(d->flags), + rc, sizeof(rc)); + snprintf(fg, sizeof(fg), "%s%s%s%s%s%s%s%s", + ((d->flags&BIT_QR)?" QR":""), + ((d->flags&BIT_AA)?" AA":""), + ((d->flags&BIT_TC)?" TC":""), + ((d->flags&BIT_RD)?" RD":""), + ((d->flags&BIT_RA)?" RA":""), + ((d->flags&BIT_Z)?" Z":""), + ((d->flags&BIT_AD)?" AD":""), + ((d->flags&BIT_CD)?" CD":"")); + if(!rrset_array_lock(d->ref, d->rrset_count, + *inf->worker->env.now)) { + /* rrsets have timed out or do not exist */ + return; + } + ssl_printf(inf->ssl, + "subnet msg %s %s %s%s %s %d %d " ARG_LL "d %d %u %u %u %d %s\n", + s, cl, tp, fg, rc, + (int)d->flags, (int)d->qdcount, + (long long)(d->ttl-*inf->worker->env.now), + (int)d->security, + (unsigned)d->an_numrrsets, + (unsigned)d->ns_numrrsets, + (unsigned)d->ar_numrrsets, + (int)d->reason_bogus, + d->reason_bogus_str?d->reason_bogus_str:""); + for(i=0; irrset_count; i++) { + struct ub_packed_rrset_key* rk = d->rrsets[i]; + struct packed_rrset_data* rd = (struct packed_rrset_data*)rk->entry.data; + size_t j; + for(j=0; jcount + rd->rrsig_count; j++) { + if(!packed_rr_to_string(rk, j, + *inf->worker->env.now, s, sizeof(s))) { + ssl_printf(inf->ssl, "BADRR\n"); + } else { + ssl_printf(inf->ssl, "%s", s); + } + } + } + rrset_array_unlock(d->ref, d->rrset_count); + ssl_printf(inf->ssl, "\n"); +} + +/** Visit an edge in subnet addrtree traverse */ +static void +addrtree_traverse_visit_edge(struct addredge* edge, addrkey_t* addr, + size_t addr_size, int is_ipv6, time_t now, struct query_info* q, + void (*func)(struct query_info*, struct reply_info*, addrkey_t*, + size_t, int, addrlen_t, int, time_t, void*), void* arg) +{ + size_t n; + addrlen_t addrlen; + if(!edge) + return; + addrlen = edge->len; + /* ceil() */ + n = (size_t)((addrlen / KEYWIDTH) + ((addrlen % KEYWIDTH != 0)?1:0)); + if(n > addr_size) + n = addr_size; + memcpy(addr, edge->str, n); + addrtree_traverse_visit_node(edge->node, addr, addr_size, is_ipv6, + now, q, func, arg); +} + +/** Visit a node in subnet addrtree traverse */ +static void +addrtree_traverse_visit_node(struct addrnode* n, addrkey_t* addr, + size_t addr_size, int is_ipv6, time_t now, struct query_info* q, + void (*func)(struct query_info*, struct reply_info*, addrkey_t*, + size_t, int, addrlen_t, int, time_t, void*), void* arg) +{ + /* If this node has data, and not expired. */ + if(n->elem && n->ttl >= now) { + func(q, (struct reply_info*)n->elem, addr, addr_size, is_ipv6, + n->scope, n->only_match_scope_zero, n->ttl, arg); + } + /* Traverse edges. */ + addrtree_traverse_visit_edge(n->edge[0], addr, addr_size, is_ipv6, + now, q, func, arg); + addrtree_traverse_visit_edge(n->edge[1], addr, addr_size, is_ipv6, + now, q, func, arg); +} + +/** Traverse subnet addrtree */ +static void +addrtree_traverse(struct addrtree* tree, int is_ipv6, time_t now, + struct query_info* q, + void (*func)(struct query_info*, struct reply_info*, addrkey_t*, + size_t, int, addrlen_t, int, time_t, void*), void* arg) +{ + uint8_t addr[16]; /* Large enough for IPv4 and IPv6. */ + memset(addr, 0, sizeof(addr)); + addrtree_traverse_visit_node(tree->root, (addrkey_t*)addr, + sizeof(addr), is_ipv6, now, q, func, arg); +} + +/** Lookup cache_lookup for subnet content. */ +static void +cache_lookup_subnet_msg(struct lruhash_entry* e, void* arg) +{ + struct cache_lookup_info* inf = (struct cache_lookup_info*)arg; + struct msgreply_entry *k = (struct msgreply_entry*)e->key; + struct subnet_msg_cache_data* d = + (struct subnet_msg_cache_data*)e->data; + if(!dname_subdomain_c(k->key.qname, inf->nm)) + return; + + if(d->tree4) { + addrtree_traverse(d->tree4, 0, *inf->worker->env.now, &k->key, + &cache_lookup_subnet_addrnode, inf); + } + if(d->tree6) { + addrtree_traverse(d->tree6, 1, *inf->worker->env.now, &k->key, + &cache_lookup_subnet_addrnode, inf); + } +} +#endif /* CLIENT_SUBNET */ + static void cache_lookup_rrset(struct lruhash_entry* e, void* arg) { @@ -1842,11 +1996,27 @@ static void do_cache_lookup_domain(RES* ssl, struct worker* worker, uint8_t* nm, size_t nmlen) { +#ifdef CLIENT_SUBNET + int m; + struct subnet_env* sn_env = NULL; +#endif /* CLIENT_SUBNET */ struct cache_lookup_info inf; inf.ssl = ssl; inf.worker = worker; inf.nm = nm; inf.nmlen = nmlen; + +#ifdef CLIENT_SUBNET + m = modstack_find(worker->env.modstack, "subnetcache"); + if(m != -1) sn_env = (struct subnet_env*)worker->env.modinfo[m]; + if(sn_env) { + lock_rw_rdlock(&sn_env->biglock); + slabhash_traverse(sn_env->subnet_msg_cache, 0, + &cache_lookup_subnet_msg, &inf); + lock_rw_unlock(&sn_env->biglock); + } +#endif /* CLIENT_SUBNET */ + slabhash_traverse(&worker->env.rrset_cache->table, 0, &cache_lookup_rrset, &inf); slabhash_traverse(worker->env.msg_cache, 0, &cache_lookup_msg, &inf); @@ -1860,6 +2030,12 @@ do_cache_lookup(RES* ssl, struct worker* worker, char* arg) size_t nmlen; int status; char* s = arg, *next = NULL; + int allow_long = 0; + + if(arg[0] == '+' && arg[1] == 't' && (arg[2]==' ' || arg[2]=='\t')) { + allow_long = 1; + s = arg+2; + } /* Find the commandline arguments of domains. */ while(s && *s != 0) { @@ -1884,6 +2060,10 @@ do_cache_lookup(RES* ssl, struct worker* worker, char* arg) sldns_get_errorstr_parse(status)); return; } + if(!allow_long && dname_count_labels(nm) < 3) { + ssl_printf(ssl, "error name too short: '%s'. Need example.com. or longer, short names take very long, use +t to allow them.\n", s); + return; + } do_cache_lookup_domain(ssl, worker, nm, nmlen); From 1e37f86ef5d501542ef731348e2780035e0ffee9 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 15 Aug 2025 13:03:31 +0200 Subject: [PATCH 19/23] - unbound-control cache_lookup +t allows tld and root names. And subnet cache contents are printed. Changelog, documentation and unit test. --- doc/Changelog | 4 + doc/unbound-control.rst | 8 +- smallapp/unbound-control.c | 3 +- .../subnet_cache_lookup.conf | 36 ++++ .../subnet_cache_lookup.dsc | 16 ++ .../subnet_cache_lookup.post | 15 ++ .../subnet_cache_lookup.pre | 42 ++++ .../subnet_cache_lookup.test | 121 ++++++++++++ .../subnet_cache_lookup.testns | 181 ++++++++++++++++++ 9 files changed, 424 insertions(+), 2 deletions(-) create mode 100644 testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.conf create mode 100644 testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.dsc create mode 100644 testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.post create mode 100644 testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.pre create mode 100644 testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.test create mode 100644 testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.testns diff --git a/doc/Changelog b/doc/Changelog index 24a218f29..b97c1fda8 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +15 August 2025: Wouter + - unbound-control cache_lookup +t allows tld and root names. And + subnet cache contents are printed. + 14 August 2025: Wouter - Fix to increase responsiveness of dump_cache. - Fix to decouple file descriptor activity and cache lookups in diff --git a/doc/unbound-control.rst b/doc/unbound-control.rst index a42809e66..bc548f51d 100644 --- a/doc/unbound-control.rst +++ b/doc/unbound-control.rst @@ -386,10 +386,16 @@ There are several commands that the server understands. Not supported in remote Unbounds in multi-process operation. -@@UAHL@unbound-control.commands@cache_lookup@@ *names* +@@UAHL@unbound-control.commands@cache_lookup@@ [``+t``] *names* Print to stdout the RRsets and messages that are in the cache. For every name listed the content at or under the name is printed. Several names separated by spaces can be given, each is printed. + When subnetcache is enabled, also matching entries from the subnet + cache are printed. + + The ``+t`` option allows tld and root names. + With it names like 'com' and '.' can be used, but it takes a lot of + effort to look up in the cache. @@UAHL@unbound-control.commands@lookup@@ *name* diff --git a/smallapp/unbound-control.c b/smallapp/unbound-control.c index c58704c82..696750c19 100644 --- a/smallapp/unbound-control.c +++ b/smallapp/unbound-control.c @@ -143,7 +143,8 @@ usage(void) printf(" load_cache load cache from stdin\n"); printf(" (not supported in remote unbounds in\n"); printf(" multi-process operation)\n"); - printf(" cache_lookup print rrsets and msgs at or under the names\n"); + printf(" cache_lookup [+t] print rrsets and msgs at or under the names\n"); + printf(" +t allow tld and root names.\n"); printf(" lookup print nameservers for name\n"); printf(" flush [+c] flushes common types for name from cache\n"); printf(" types: A, AAAA, MX, PTR, NS,\n"); diff --git a/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.conf b/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.conf new file mode 100644 index 000000000..81072c70b --- /dev/null +++ b/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.conf @@ -0,0 +1,36 @@ +server: + verbosity: 7 + # num-threads: 1 + interface: 127.0.0.1 + interface: 127.0.0.1@@PROXYPORT@ + port: @PORT@ + proxy-protocol-port: @PROXYPORT@ + access-control: 1.0.0.0/8 allow + use-syslog: no + directory: "" + pidfile: "unbound.pid" + chroot: "" + username: "" + do-not-query-localhost: no + target-fetch-policy: "0 0 0 0 0" + send-client-subnet: 127.0.0.1 + max-client-subnet-ipv4: 17 + module-config: "subnetcache iterator" + qname-minimisation: no + minimal-responses: no +remote-control: + control-enable: yes + control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@ + control-use-cert: no +stub-zone: + name: "." + stub-prime: no + stub-addr: "127.0.0.1@@TOPORT@" +stub-zone: + name: "example.com" + stub-prime: no + stub-addr: "127.0.0.1@@TOPORT@" +stub-zone: + name: "example.net" + stub-prime: no + stub-addr: "127.0.0.1@@TOPORT@" diff --git a/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.dsc b/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.dsc new file mode 100644 index 000000000..5f478e935 --- /dev/null +++ b/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.dsc @@ -0,0 +1,16 @@ +BaseName: subnet_cache_lookup +Version: 1.0 +Description: Subnet cache contents with unbound-control cache_lookup +CreationDate: Fri Aug 15 11:00:00 CEST 2025 +Maintainer: dr. W.C.A. Wijngaards +Category: +Component: +CmdDepends: +Depends: +Help: +Pre: subnet_cache_lookup.pre +Post: subnet_cache_lookup.post +Test: subnet_cache_lookup.test +AuxFiles: +Passed: +Failure: diff --git a/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.post b/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.post new file mode 100644 index 000000000..247ea68a6 --- /dev/null +++ b/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.post @@ -0,0 +1,15 @@ +# #-- subnet_cache_lookup.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 +echo "> cat logfiles" +kill_pid $FWD_PID +kill_pid $UNBOUND_PID +rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID +cat fwd.log +cat unbound.log diff --git a/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.pre b/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.pre new file mode 100644 index 000000000..ce007c4fa --- /dev/null +++ b/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.pre @@ -0,0 +1,42 @@ +# #-- subnet_cache_lookup.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 CLIENT_SUBNET 1" $PRE/config.h; then echo test enabled; else skip_test "test skipped"; fi + +get_make +(cd $PRE; $MAKE streamtcp) + +get_random_port 3 +UNBOUND_PORT=$RND_PORT +PROXY_PORT=$(($RND_PORT + 1)) +FWD_PORT=$(($RND_PORT + 2)) +echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test +echo "PROXY_PORT=$PROXY_PORT" >> .tpkg.var.test +echo "FWD_PORT=$FWD_PORT" >> .tpkg.var.test + +# start forwarder +get_ldns_testns +$LDNS_TESTNS -p $FWD_PORT subnet_cache_lookup.testns >fwd.log 2>&1 & +FWD_PID=$! +echo "FWD_PID=$FWD_PID" >> .tpkg.var.test + +# make config file +CONTROL_PATH=/tmp +CONTROL_PID=$$ +sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@PROXYPORT\@/'$PROXY_PORT'/' -e 's/@TOPORT\@/'$FWD_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < subnet_cache_lookup.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 +echo "CONTROL_PATH=$CONTROL_PATH" >> .tpkg.var.test +echo "CONTROL_PID=$CONTROL_PID" >> .tpkg.var.test + +cat .tpkg.var.test +wait_ldns_testns_up fwd.log +wait_unbound_up unbound.log + diff --git a/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.test b/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.test new file mode 100644 index 000000000..8838a64ed --- /dev/null +++ b/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.test @@ -0,0 +1,121 @@ +# #-- subnet_cache_lookup.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 +echo "> dig www.example.com." +dig @127.0.0.1 -p $UNBOUND_PORT www.example.com. | tee outfile +if grep SERVFAIL outfile; then + echo "> try again" + dig @127.0.0.1 -p $UNBOUND_PORT www.example.com. | tee outfile +fi +if grep SERVFAIL outfile; then + echo "> try again" + sleep 1 + dig @127.0.0.1 -p $UNBOUND_PORT www.example.com. | tee outfile +fi +if grep SERVFAIL outfile; then + echo "> try again" + sleep 1 + dig @127.0.0.1 -p $UNBOUND_PORT www.example.com. | tee outfile +fi +if grep SERVFAIL outfile; then + echo "> try again" + sleep 1 + dig @127.0.0.1 -p $UNBOUND_PORT www.example.com. | tee outfile +fi +if grep SERVFAIL outfile; then + echo "> try again" + sleep 10 + dig @127.0.0.1 -p $UNBOUND_PORT www.example.com. | tee outfile +fi +if grep SERVFAIL outfile; then + echo "> try again" + sleep 10 + dig @127.0.0.1 -p $UNBOUND_PORT www.example.com. | tee outfile +fi +#echo "> cat logfiles" +#cat fwd.log +#cat unbound.log +echo "> check answer" +if grep www.example.com outfile | grep "10.20.30.40"; then + echo "OK" +else + echo "Not OK" + exit 1 +fi + +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 cache_lookup example.com" +$PRE/unbound-control -c ub.conf cache_lookup example.com 2>&1 | tee outfile +if test $? -ne 0; then + echo "wrong exit value." + exit 1 +fi +echo "> check unbound-control output" +if grep "subnet" outfile; then + echo "OK" +else + echo "Not OK" + exit 1 +fi + +echo "> use proxy-protocol to put more addresses in the edns subnet cache" +$PRE/streamtcp -f 127.0.0.1@$PROXY_PORT -p 1.1.3.4 www.example.net. A IN | tee outfile +if grep www.example.net outfile | grep "10.20.30.41"; then + echo "OK" +else + echo "Not OK" + exit 1 +fi + +$PRE/streamtcp -f 127.0.0.1@$PROXY_PORT -p 1.2.3.4 www.example.net. A IN | tee outfile +if grep www.example.net outfile | grep "10.20.30.42"; then + echo "OK" +else + echo "Not OK" + exit 1 +fi + +$PRE/streamtcp -f 127.0.0.1@$PROXY_PORT -p 1.3.3.4 www.example.net. A IN | tee outfile +if grep www.example.net outfile | grep "10.20.30.43"; then + echo "OK" +else + echo "Not OK" + exit 1 +fi + +$PRE/streamtcp -f 127.0.0.1@$PROXY_PORT -p 1.4.3.4 www.example.net. A IN | tee outfile +if grep www.example.net outfile | grep "10.20.30.44"; then + echo "OK" +else + echo "Not OK" + exit 1 +fi + +echo "> unbound-control cache_lookup example.net" +$PRE/unbound-control -c ub.conf cache_lookup example.net 2>&1 | tee outfile +if test $? -ne 0; then + echo "wrong exit value." + exit 1 +fi +echo "> check unbound-control output" +if grep "subnet" outfile; then + echo "OK" +else + echo "Not OK" + exit 1 +fi + +exit 0 diff --git a/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.testns b/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.testns new file mode 100644 index 000000000..ebdbffa71 --- /dev/null +++ b/testdata/subnet_cache_lookup.tdir/subnet_cache_lookup.testns @@ -0,0 +1,181 @@ +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS test.ns. +SECTION ADDITIONAL +test.ns. IN A 127.0.0.1 +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +;MATCH opcode qtype qname ednsdata +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +HEX_EDNSDATA_BEGIN + ; client is 127.0.0.1 + 00 08 ; OPC + 00 07 ; option length + 00 01 ; Family + 11 11 ; source mask, scopemask + 7f 00 00 ; address +HEX_EDNSDATA_END +ns.example.com. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +example.com. IN SOA +SECTION ANSWER +example.com. IN SOA ns.example.com. hostmaster.example.com. 1 3600 900 86400 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname ednsdata +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.net. IN A +SECTION ADDITIONAL +HEX_EDNSDATA_BEGIN + ; client is 1.1.3.4 + 00 08 ; OPC + 00 07 ; option length + 00 01 ; Family + 11 00 ; source mask, scopemask + 01 01 00 ; address +HEX_EDNSDATA_END +HEX_ANSWER_BEGIN + 00 00 84 00 00 01 00 01 ;ID 0, QR AA + 00 00 00 01 03 77 77 77 ; www.example.net. A? (DO) + 07 65 78 61 6d 70 6c 65 + 03 6e 65 74 00 00 01 00 + 01 + ; www.example.net. A 10.20.30.41 + 03 77 77 77 07 65 78 61 6d 70 6c 65 03 6e 65 74 00 + 00 01 00 01 00 00 0e 10 00 04 + 0a 14 1e 29 + + 00 00 29 10 00 00 00 + 80 00 00 0b + 00 08 00 07 ; OPC, optlen + 00 01 11 11 ; ip4, scope 17, source 17 + 01 01 00 ;1.1.0.0/17 +HEX_ANSWER_END +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname ednsdata +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.net. IN A +SECTION ADDITIONAL +HEX_EDNSDATA_BEGIN + ; client is 1.2.3.4 + 00 08 ; OPC + 00 07 ; option length + 00 01 ; Family + 11 00 ; source mask, scopemask + 01 02 00 ; address +HEX_EDNSDATA_END +HEX_ANSWER_BEGIN + 00 00 84 00 00 01 00 01 ;ID 0, QR AA + 00 00 00 01 03 77 77 77 ; www.example.net. A? (DO) + 07 65 78 61 6d 70 6c 65 + 03 6e 65 74 00 00 01 00 + 01 + ; www.example.net. A 10.20.30.42 + 03 77 77 77 07 65 78 61 6d 70 6c 65 03 6e 65 74 00 + 00 01 00 01 00 00 0e 10 00 04 + 0a 14 1e 2a + + 00 00 29 10 00 00 00 + 80 00 00 0b + 00 08 00 07 ; OPC, optlen + 00 01 11 11 ; ip4, scope 17, source 17 + 01 02 00 ;1.2.0.0/17 +HEX_ANSWER_END +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname ednsdata +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.net. IN A +SECTION ADDITIONAL +HEX_EDNSDATA_BEGIN + ; client is 1.3.3.4 + 00 08 ; OPC + 00 07 ; option length + 00 01 ; Family + 11 00 ; source mask, scopemask + 01 03 00 ; address +HEX_EDNSDATA_END +HEX_ANSWER_BEGIN + 00 00 84 00 00 01 00 01 ;ID 0, QR AA + 00 00 00 01 03 77 77 77 ; www.example.net. A? (DO) + 07 65 78 61 6d 70 6c 65 + 03 6e 65 74 00 00 01 00 + 01 + ; www.example.net. A 10.20.30.43 + 03 77 77 77 07 65 78 61 6d 70 6c 65 03 6e 65 74 00 + 00 01 00 01 00 00 0e 10 00 04 + 0a 14 1e 2b + + 00 00 29 10 00 00 00 + 80 00 00 0b + 00 08 00 07 ; OPC, optlen + 00 01 11 11 ; ip4, scope 17, source 17 + 01 03 00 ;1.3.0.0/17 +HEX_ANSWER_END +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname ednsdata +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.net. IN A +SECTION ADDITIONAL +HEX_EDNSDATA_BEGIN + ; client is 1.4.3.4 + 00 08 ; OPC + 00 07 ; option length + 00 01 ; Family + 11 00 ; source mask, scopemask + 01 04 00 ; address +HEX_EDNSDATA_END +HEX_ANSWER_BEGIN + 00 00 84 00 00 01 00 01 ;ID 0, QR AA + 00 00 00 01 03 77 77 77 ; www.example.net. A? (DO) + 07 65 78 61 6d 70 6c 65 + 03 6e 65 74 00 00 01 00 + 01 + ; www.example.net. A 10.20.30.44 + 03 77 77 77 07 65 78 61 6d 70 6c 65 03 6e 65 74 00 + 00 01 00 01 00 00 0e 10 00 04 + 0a 14 1e 2c + + 00 00 29 10 00 00 00 + 80 00 00 0b + 00 08 00 07 ; OPC, optlen + 00 01 11 11 ; ip4, scope 17, source 17 + 01 04 00 ;1.4.0.0/17 +HEX_ANSWER_END +ENTRY_END From 13bb78a74000ad672edc27e9f8028edfc441b602 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 15 Aug 2025 14:06:54 +0200 Subject: [PATCH 20/23] - Fix cache_lookup subnet printout to wipe zero part of the prefix. --- daemon/remote.c | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/remote.c b/daemon/remote.c index 549979990..9d9e53703 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -1849,6 +1849,7 @@ addrtree_traverse_visit_edge(struct addredge* edge, addrkey_t* addr, n = (size_t)((addrlen / KEYWIDTH) + ((addrlen % KEYWIDTH != 0)?1:0)); if(n > addr_size) n = addr_size; + memset(addr, 0, addr_size); memcpy(addr, edge->str, n); addrtree_traverse_visit_node(edge->node, addr, addr_size, is_ipv6, now, q, func, arg); From 523710f371335be13587282f7662c20f692c085c Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 15 Aug 2025 14:07:05 +0200 Subject: [PATCH 21/23] - Fix cache_lookup subnet printout to wipe zero part of the prefix. Changelog entry. --- doc/Changelog | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/Changelog b/doc/Changelog index b97c1fda8..50d972140 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,7 @@ 15 August 2025: Wouter - unbound-control cache_lookup +t allows tld and root names. And subnet cache contents are printed. + - Fix cache_lookup subnet printout to wipe zero part of the prefix. 14 August 2025: Wouter - Fix to increase responsiveness of dump_cache. From 8fd4b91afc363b2b43f5b481483c8b5ea4f1d773 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 15 Aug 2025 16:04:23 +0200 Subject: [PATCH 22/23] - Fix cache_lookup subnet print to not print messages without rrsets and perform in-depth check on node in the addrtree. --- daemon/remote.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 9d9e53703..25c390c82 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -1785,10 +1785,6 @@ cache_lookup_subnet_addrnode(struct query_info* q, struct reply_info* d, sldns_wire2str_dname_buf(q->qname, q->qname_len, s, sizeof(s)); sldns_wire2str_type_buf(q->qtype, tp, sizeof(tp)); sldns_wire2str_class_buf(q->qclass, cl, sizeof(cl)); - if(!ssl_printf(inf->ssl, "subnet %s/%d%s %s %s %s " ARG_LL "d\n", astr, - (int)scope, (only_match_scope_zero?" scope_zero":""), - s, cl, tp, (long long)(ttl-*inf->worker->env.now))) - return; sldns_wire2str_rcode_buf(FLAGS_GET_RCODE(d->flags), rc, sizeof(rc)); snprintf(fg, sizeof(fg), "%s%s%s%s%s%s%s%s", @@ -1805,6 +1801,12 @@ cache_lookup_subnet_addrnode(struct query_info* q, struct reply_info* d, /* rrsets have timed out or do not exist */ return; } + if(!ssl_printf(inf->ssl, "subnet %s/%d%s %s %s %s " ARG_LL "d\n", astr, + (int)scope, (only_match_scope_zero?" scope_zero":""), + s, cl, tp, (long long)(ttl-*inf->worker->env.now))) { + rrset_array_unlock(d->ref, d->rrset_count); + return; + } ssl_printf(inf->ssl, "subnet msg %s %s %s%s %s %d %d " ARG_LL "d %d %u %u %u %d %s\n", s, cl, tp, fg, rc, @@ -1842,7 +1844,7 @@ addrtree_traverse_visit_edge(struct addredge* edge, addrkey_t* addr, { size_t n; addrlen_t addrlen; - if(!edge) + if(!edge || !edge->node) return; addrlen = edge->len; /* ceil() */ From 1d877400eaf1fded5d60ecdfe37d2f4badf81935 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 15 Aug 2025 16:04:34 +0200 Subject: [PATCH 23/23] - Fix cache_lookup subnet print to not print messages without rrsets and perform in-depth check on node in the addrtree. --- doc/Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 50d972140..88cd08848 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,8 @@ - unbound-control cache_lookup +t allows tld and root names. And subnet cache contents are printed. - Fix cache_lookup subnet printout to wipe zero part of the prefix. + - Fix cache_lookup subnet print to not print messages without rrsets + and perform in-depth check on node in the addrtree. 14 August 2025: Wouter - Fix to increase responsiveness of dump_cache.