diff --git a/daemon/remote.c b/daemon/remote.c index 8a8edb8eb..4e4647237 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -3233,6 +3233,7 @@ do_auth_zone_reload(RES* ssl, struct worker* worker, char* arg) z->zone_expired = 0; if(xfr) { xfr->zone_expired = 0; + xfr->num_ixfrs = 0; if(!xfr_find_soa(z, xfr)) { if(z->data.count == 0) { lock_rw_unlock(&z->lock); diff --git a/doc/Changelog b/doc/Changelog index f9396cb2b..a539d404d 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -4,6 +4,10 @@ Default is disabled. This hardens against unbounded transfers. Thanks to Qifan Zhang, Palo Alto Networks, for the report. + - Fix perform a full transfer every number of incremental + transfers, to stop increasing memory usage, for auth-zone + and rpz zones. Thanks to Qifan Zhang, Palo Alto Networks, + for the report. 12 June 2026: Wouter - Fix that for auth-zone and rpz zones the allow-notify diff --git a/doc/unbound.conf.rst b/doc/unbound.conf.rst index cfd78806e..b96f1722b 100644 --- a/doc/unbound.conf.rst +++ b/doc/unbound.conf.rst @@ -5130,6 +5130,10 @@ answer queries with that content. because it may not have that when retrieving that data, instead use a plain IP address to avoid a circular dependency on retrieving that IP address. + Every number of IXFR transfers, a full AXFR is performed. + This is to consolidate the rpz memory, that would otherwise grow. + The fixed value is after 5 IXFR transfers. + @@UAHL@unbound.conf.rpz@master@@: ** Alternate syntax for :ref:`primary`. diff --git a/services/authzone.c b/services/authzone.c index 1ab2ec1a4..3d31e4674 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -96,6 +96,8 @@ /** number of timeouts before we fallback from IXFR to AXFR, * because some versions of servers (eg. dnsmasq) drop IXFR packets. */ #define NUM_TIMEOUTS_FALLBACK_IXFR 3 +/** number of IXFRs before an AXFR is performed, to consolidate RPZ memory. */ +#define NUM_IXFR_BEFORE_AXFR 5 /** pick up nextprobe task to start waiting to perform transfer actions */ static void xfr_set_timeout(struct auth_xfer* xfr, struct module_env* env, @@ -2087,6 +2089,7 @@ auth_xfer_setup(struct auth_zone* z, struct auth_xfer* x) if(!xfr_find_soa(z, x)) { return 1; } + x->is_rpz = (z->rpz!=NULL); /* nothing for probe, nextprobe and transfer tasks */ return 1; } @@ -4317,7 +4320,7 @@ xfr_create_ixfr_packet(struct auth_xfer* xfr, sldns_buffer* buf, uint16_t id, { struct query_info qinfo; uint32_t serial; - int have_zone; + int have_zone, get_full = 0; have_zone = xfr->have_zone; serial = xfr->serial; @@ -4330,7 +4333,18 @@ xfr_create_ixfr_packet(struct auth_xfer* xfr, sldns_buffer* buf, uint16_t id, xfr->task_transfer->on_ixfr_is_axfr = 0; xfr->task_transfer->on_ixfr = 1; qinfo.qtype = LDNS_RR_TYPE_IXFR; - if(!have_zone || xfr->task_transfer->ixfr_fail || !master->ixfr) { + if(xfr->num_ixfrs >= NUM_IXFR_BEFORE_AXFR && xfr->is_rpz) { + /* For the RPZ, an IXFR is going to grow regions, and a + * full transfer, zonefile read, AXFR and HTTP clear the + * region, but IXFR does not. That memory keeps growing, + * and getting a full transfer with AXFR here resets that. + * The rpz->client_set->region, rpz->ns_set->region and + * rpz->respip_set->region need to be reset, they are for + * rpz-client-ip, rpz-nsip and rpz-ip. */ + get_full = 1; + } + if(!have_zone || xfr->task_transfer->ixfr_fail || !master->ixfr + || get_full) { qinfo.qtype = LDNS_RR_TYPE_AXFR; xfr->task_transfer->ixfr_fail = 0; xfr->task_transfer->on_ixfr = 0; @@ -4955,6 +4969,8 @@ apply_ixfr(struct auth_xfer* xfr, struct auth_zone* z, int delmode = 0; int softfail = 0; + xfr->num_ixfrs++; + /* start RR iterator over chunklist of packets */ chunk_rrlist_start(xfr, &rr_chunk, &rr_num, &rr_pos); while(!chunk_rrlist_end(rr_chunk, rr_num)) { @@ -5100,6 +5116,7 @@ apply_axfr(struct auth_xfer* xfr, struct auth_zone* z, xfr->have_zone = 0; xfr->serial = 0; xfr->soa_zone_acquired = 0; + xfr->num_ixfrs = 0; /* insert all RRs in to the zone */ /* insert the SOA only once, skip the last one */ @@ -5202,6 +5219,7 @@ apply_http(struct auth_xfer* xfr, struct auth_zone* z, xfr->have_zone = 0; xfr->serial = 0; xfr->soa_zone_acquired = 0; + xfr->num_ixfrs = 0; chunk = xfr->task_transfer->chunks_first; chunk_pos = 0; @@ -5414,6 +5432,7 @@ xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env, } z->soa_zone_acquired = *env->now; xfr->soa_zone_acquired = *env->now; + xfr->is_rpz = (z->rpz!=NULL); /* release xfr lock while verifying zonemd because it may have * to spawn lookups in the state machines */ diff --git a/services/authzone.h b/services/authzone.h index 3862c6431..23a6b0f93 100644 --- a/services/authzone.h +++ b/services/authzone.h @@ -292,6 +292,10 @@ struct auth_xfer { size_t max_transfer_size; /** The maximum auth zone transfer time taken, in msec. */ int max_transfer_time; + /** the zone is an rpz zone */ + int is_rpz; + /** the number of IXFRs since the last full transfer. */ + int num_ixfrs; }; /**