]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix to add `max-transfer-size` and `max-transfer-time` that
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Mon, 15 Jun 2026 13:45:03 +0000 (15:45 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Mon, 15 Jun 2026 13:45:03 +0000 (15:45 +0200)
  limit auth-zone and rpz transfer amount and time taken.
  Default is disabled. This hardens against unbounded
  transfers. Thanks to Qifan Zhang, Palo Alto Networks,
  for the report.

15 files changed:
daemon/remote.c
doc/Changelog
doc/example.conf.in
doc/unbound.conf.rst
services/authzone.c
services/authzone.h
testdata/auth_transfer_limit.tdir/auth_transfer_limit.conf [new file with mode: 0644]
testdata/auth_transfer_limit.tdir/auth_transfer_limit.dsc [new file with mode: 0644]
testdata/auth_transfer_limit.tdir/auth_transfer_limit.post [new file with mode: 0644]
testdata/auth_transfer_limit.tdir/auth_transfer_limit.pre [new file with mode: 0644]
testdata/auth_transfer_limit.tdir/auth_transfer_limit.test [new file with mode: 0644]
testdata/auth_transfer_limit.tdir/auth_transfer_limit.testns [new file with mode: 0644]
util/config_file.h
util/configlexer.lex
util/configparser.y

index dc241b09c510936d25522079d575e7d7e868f4cc..8a8edb8ebff79b3ecd709f0ed535c27838e39163 100644 (file)
@@ -5461,6 +5461,23 @@ xfr_masterlist_equal(struct auth_master* list1, struct auth_master* list2)
        return 0;
 }
 
+/** See if configuration has changed. */
+static int
+xfr_config_equal(struct auth_xfer* xfr1, struct auth_xfer* xfr2)
+{
+       if(xfr1 == NULL && xfr2 == NULL)
+               return 1;
+       if(xfr1 == NULL && xfr2 != NULL)
+               return 0;
+       if(xfr1 != NULL && xfr2 == NULL)
+               return 0;
+       if(xfr1->max_transfer_size != xfr2->max_transfer_size)
+               return 0;
+       if(xfr1->max_transfer_time != xfr2->max_transfer_time)
+               return 0;
+       return 1;
+}
+
 /** See if the list of masters has changed. */
 static int
 xfr_masters_equal(struct auth_xfer* xfr1, struct auth_xfer* xfr2)
@@ -5572,6 +5589,7 @@ auth_zones_check_changes(struct fast_reload_thread* fr,
                         */
                        if(have_old != have_new || old_serial != new_serial
                                || !xfr_masters_equal(old_xfr, new_xfr)
+                               || !xfr_config_equal(old_xfr, new_xfr)
                                || old_z->zonemd_callback_env != NULL) {
                                /* The zone has been changed. */
                                if(!fr_add_auth_zone_change(fr, old_z, new_z,
@@ -7702,6 +7720,9 @@ auth_xfr_pickup_config(struct auth_xfer* loadxfr, struct auth_xfer* xfr)
        log_assert(loadxfr->namelabs == xfr->namelabs);
        log_assert(loadxfr->dclass == xfr->dclass);
 
+       xfr->max_transfer_size = loadxfr->max_transfer_size;
+       xfr->max_transfer_time =  loadxfr->max_transfer_time;
+
        /* The lists can be swapped in, the other xfr struct will be deleted
         * afterwards. */
        probe_masters = xfr->task_probe->masters;
index e97466738c1c20d2af79582c97af093f88b9cd7f..f9396cb2b8bb6895a359409f78a41a5512324e42 100644 (file)
@@ -1,3 +1,10 @@
+15 June 2026: Wouter
+       - Fix to add `max-transfer-size` and `max-transfer-time` that
+         limit auth-zone and rpz transfer amount and time taken.
+         Default is disabled. This hardens against unbounded
+         transfers. 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
          addresses and netblocks are available from start, and
index 2c6d63409dad26cbd6d3fdee0d92220118eb823c..5250ef456e1e805930cfb560f1853c92c7259df4 100644 (file)
@@ -1287,6 +1287,9 @@ remote-control:
 #      zonemd-check: no
 #      zonemd-reject-absence: no
 #      zonefile: "example.org.zone"
+#      max-transfer-size: 0
+#      max-transfer-time: 0
+
 
 # Views
 # Create named views. Name must be unique.
@@ -1453,3 +1456,5 @@ remote-control:
 #     rpz-signal-nxdomain-ra: no
 #     for-downstream: no
 #     tags: "example"
+#     max-transfer-size: 0
+#     max-transfer-time: 0
index 97fc738a462a6b8fc03298a60be405768b53f425..cfd78806eaa5d668353c7d8f631d612f338cc2b4 100644 (file)
@@ -4025,6 +4025,31 @@ fallback activates to fetch from the upstream instead of the SERVFAIL.
     If the file does not exist or is empty, Unbound will attempt to fetch zone
     data (eg. from the primary servers).
 
+
+@@UAHL@unbound.conf.auth@max-transfer-size@@: *<number>*
+    Number of bytes size of the maximum zone transfer size.
+    Larger transfers, over AXFR, IXFR and HTTP, are not allowed.
+    A plain number is in bytes, append 'k', 'm' or 'g' for kilobytes, megabytes
+    or gigabytes (1024*1024 bytes in a megabyte).
+    The value ``0`` disables the feature.
+
+    Only consider for untrusted/misbehaving primaries that could hog resources
+    and bring down the resolver.
+
+    Default: 0
+
+
+@@UAHL@unbound.conf.auth@max-transfer-time@@: *<msec>*
+    Maximum time in milliseconds that a zone transfer is allowed to take from
+    the start.
+    The value ``0`` disables the feature.
+
+    Only consider for untrusted/misbehaving primaries that could hog resources
+    and bring down the resolver.
+
+    Default: 0
+
+
 .. _unbound.conf.view:
 
 View Options
@@ -5205,6 +5230,31 @@ answer queries with that content.
     If no tags are specified the policies from this section will be applied for
     all clients.
 
+
+@@UAHL@unbound.conf.rpz@max-transfer-size@@: *<number>*
+    Number of bytes size of the maximum zone transfer size.
+    Larger transfers, over AXFR, IXFR and HTTP, are not allowed.
+    A plain number is in bytes, append 'k', 'm' or 'g' for kilobytes, megabytes
+    or gigabytes (1024*1024 bytes in a megabyte).
+    The value ``0`` disables the feature.
+
+    Only consider for untrusted/misbehaving primaries that could hog resources
+    and bring down the resolver.
+
+    Default: 0
+
+
+@@UAHL@unbound.conf.rpz@max-transfer-time@@: *<msec>*
+    Maximum time in milliseconds that a zone transfer is allowed to take from
+    the start.
+    The value ``0`` disables the feature.
+
+    Only consider for untrusted/misbehaving primaries that could hog resources
+    and bring down the resolver.
+
+    Default: 0
+
+
 Memory Control Example
 ----------------------
 
index 3fc3f174ec0639c149da65a73b967971a4624625..1ab2ec1a488c4b6dbfbd3c15bbee5bab9aaf82a6 100644 (file)
@@ -55,6 +55,7 @@
 #include "util/log.h"
 #include "util/module.h"
 #include "util/random.h"
+#include "util/timeval_func.h"
 #include "services/cache/dns.h"
 #include "services/outside_network.h"
 #include "services/listen_dnsport.h"
@@ -2145,6 +2146,9 @@ auth_zones_cfg(struct auth_zones* az, struct config_auth* c)
                }
                return 0;
        }
+       /* Populate the xfer related options early since we may create one now */
+       z->max_transfer_size = c->max_transfer_size;
+       z->max_transfer_time = c->max_transfer_time;
        if(c->masters || c->urls) {
                if(!(x=auth_zones_find_or_add_xfer(az, z))) {
                        lock_rw_unlock(&az->lock);
@@ -2327,6 +2331,7 @@ auth_chunks_delete(struct auth_transfer* at)
        }
        at->chunks_first = NULL;
        at->chunks_last = NULL;
+       at->chunks_total = 0;
 }
 
 /** free master addr list */
@@ -5592,6 +5597,7 @@ xfr_transfer_init_fetch(struct auth_xfer* xfr, struct module_env* env)
         t.tv_sec = timeout/1000;
         t.tv_usec = (timeout%1000)*1000;
 #endif
+       xfr->task_transfer->start_time = *env->now_tv;
 
        if(master->http) {
                /* perform http fetch */
@@ -6146,6 +6152,7 @@ xfer_link_data(sldns_buffer* pkt, struct auth_xfer* xfr)
        if(xfr->task_transfer->chunks_last)
                xfr->task_transfer->chunks_last->next = e;
        xfr->task_transfer->chunks_last = e;
+       xfr->task_transfer->chunks_total += e->len;
        return 1;
 }
 
@@ -6241,6 +6248,15 @@ auth_xfer_transfer_timer_callback(void* arg)
        xfr_transfer_nexttarget_or_end(xfr, env);
 }
 
+/** return the time taken by the transfer */
+static int
+auth_xfer_transfer_time_taken(struct auth_xfer* xfr, struct module_env* env)
+{
+       struct timeval delta;
+       timeval_subtract(&delta, env->now_tv, &xfr->task_transfer->start_time);
+       return ((int)delta.tv_sec)*1000 + ((int)delta.tv_usec)/1000;
+}
+
 /** callback for task_transfer tcp connections */
 int
 auth_xfer_transfer_tcp_callback(struct comm_point* c, void* arg, int err,
@@ -6307,6 +6323,15 @@ auth_xfer_transfer_tcp_callback(struct comm_point* c, void* arg, int err,
                        xfr->task_transfer->master->host);
                goto failed;
        }
+       if(xfr->max_transfer_size > 0 &&
+               xfr->task_transfer->chunks_total > xfr->max_transfer_size) {
+               char zname[LDNS_MAX_DOMAINLEN];
+               dname_str(xfr->name, zname);
+               log_err("auth zone %s transfer from %s exceeded %u bytes, aborting",
+                       zname, xfr->task_transfer->master->host,
+                       (unsigned)xfr->max_transfer_size);
+               goto failed;
+       }
        /* if the transfer is done now, disconnect and process the list */
        if(transferdone) {
                comm_point_delete(xfr->task_transfer->cp);
@@ -6315,6 +6340,16 @@ auth_xfer_transfer_tcp_callback(struct comm_point* c, void* arg, int err,
                return 0;
        }
 
+       if(xfr->max_transfer_time > 0 &&
+               auth_xfer_transfer_time_taken(xfr, env) > xfr->max_transfer_time) {
+               char zname[LDNS_MAX_DOMAINLEN];
+               dname_str(xfr->name, zname);
+               log_err("auth zone %s transfer from %s exceeded %u msec total running time, aborting",
+                       zname, xfr->task_transfer->master->host,
+                       (unsigned)xfr->max_transfer_time);
+               goto failed;
+       }
+
        /* if we want to read more messages, setup the commpoint to read
         * a DNS packet, and the timeout */
        lock_basic_unlock(&xfr->lock);
@@ -6370,6 +6405,16 @@ auth_xfer_transfer_http_callback(struct comm_point* c, void* arg, int err,
                                xfr->task_transfer->master->host);
                        goto failed;
                }
+               if(xfr->max_transfer_size > 0 &&
+                       xfr->task_transfer->chunks_total > xfr->max_transfer_size) {
+                       char zname[LDNS_MAX_DOMAINLEN];
+                       dname_str(xfr->name, zname);
+                       log_err("auth zone %s http %s/%s exceeded %u bytes, aborting",
+                               zname, xfr->task_transfer->master->host,
+                               xfr->task_transfer->master->file,
+                               (unsigned)xfr->max_transfer_size);
+                       goto failed;
+               }
        }
        /* if the transfer is done now, disconnect and process the list */
        if(err == NETEVENT_DONE) {
@@ -6381,6 +6426,17 @@ auth_xfer_transfer_http_callback(struct comm_point* c, void* arg, int err,
                return 0;
        }
 
+       if(xfr->max_transfer_time > 0 &&
+               auth_xfer_transfer_time_taken(xfr, env) > xfr->max_transfer_time) {
+               char zname[LDNS_MAX_DOMAINLEN];
+               dname_str(xfr->name, zname);
+               log_err("auth zone %s transfer http %s/%s exceeded %u msec total running time, aborting",
+                       zname, xfr->task_transfer->master->host,
+                       xfr->task_transfer->master->file,
+                       (unsigned)xfr->max_transfer_time);
+               goto failed;
+       }
+
        /* if we want to read more messages, setup the commpoint to read
         * a DNS packet, and the timeout */
        lock_basic_unlock(&xfr->lock);
@@ -7172,6 +7228,8 @@ auth_xfer_new(struct auth_zone* z)
        xfr->namelen = z->namelen;
        xfr->namelabs = z->namelabs;
        xfr->dclass = z->dclass;
+       xfr->max_transfer_size = z->max_transfer_size;
+       xfr->max_transfer_time = z->max_transfer_time;
 
        xfr->task_nextprobe = (struct auth_nextprobe*)calloc(1,
                sizeof(struct auth_nextprobe));
index 2086a99ee88616bcc74d4ebd9494829f025c6827..3862c6431b2c47c3e9492ee0c1c0da55cb9ec466 100644 (file)
@@ -153,6 +153,10 @@ struct auth_zone {
        struct auth_zone* rpz_az_next;
        /** previous auth zone containing RPZ data, or NULL */
        struct auth_zone* rpz_az_prev;
+       /** The maximum auth zone transfer size, in bytes. */
+       size_t max_transfer_size;
+       /** The maximum auth zone transfer time taken, in msec. */
+       int max_transfer_time;
 };
 
 /**
@@ -283,6 +287,11 @@ struct auth_xfer {
         * this is renewed every SOA probe and transfer.  On zone load
         * from zonefile it is also set (with probe set soon to check) */
        time_t lease_time;
+
+       /** The maximum auth zone transfer size, in bytes. */
+       size_t max_transfer_size;
+       /** The maximum auth zone transfer time taken, in msec. */
+       int max_transfer_time;
 };
 
 /**
@@ -379,6 +388,10 @@ struct auth_transfer {
        struct auth_chunk* chunks_first;
        /** last element in chunks list (to append new data at the end) */
        struct auth_chunk* chunks_last;
+       /** running total of bytes held in chunks_first..chunks_last */
+       size_t chunks_total;
+       /** start time of the transfer */
+       struct timeval start_time;
 
        /** list of upstream masters for this zone, from config */
        struct auth_master* masters;
diff --git a/testdata/auth_transfer_limit.tdir/auth_transfer_limit.conf b/testdata/auth_transfer_limit.tdir/auth_transfer_limit.conf
new file mode 100644 (file)
index 0000000..94b4475
--- /dev/null
@@ -0,0 +1,32 @@
+server:
+       verbosity: 7
+       # num-threads: 1
+       interface: 127.0.0.1
+       port: @PORT@
+       use-syslog: no
+       directory: ""
+       pidfile: "unbound.pid"
+       chroot: ""
+       username: ""
+       do-not-query-localhost: no
+       use-caps-for-id: no
+
+auth-zone:
+       name: "example.com"
+       for-upstream: yes
+       for-downstream: yes
+       master: "127.0.0.1@@TOPORT@"
+       max-transfer-size: 512
+       max-transfer-time: 2000
+auth-zone:
+       name: "example2.com"
+       for-upstream: yes
+       for-downstream: yes
+       master: "127.0.0.1@@TOPORT@"
+       max-transfer-size: 512
+       max-transfer-time: 2000
+remote-control:
+       control-enable: yes
+       control-interface: @CONTROL_PATH@/controlpipe.@CONTROL_PID@
+       control-use-cert: no
+
diff --git a/testdata/auth_transfer_limit.tdir/auth_transfer_limit.dsc b/testdata/auth_transfer_limit.tdir/auth_transfer_limit.dsc
new file mode 100644 (file)
index 0000000..926099e
--- /dev/null
@@ -0,0 +1,16 @@
+BaseName: auth_transfer_limit
+Version: 1.0
+Description: Test limit of authority zone transfer.
+CreationDate: Tue May 12 03:00:00 PM CEST 2026
+Maintainer: dr. W.C.A. Wijngaards
+Category: 
+Component:
+CmdDepends: 
+Depends: 
+Help:
+Pre: auth_transfer_limit.pre
+Post: auth_transfer_limit.post
+Test: auth_transfer_limit.test
+AuxFiles: 
+Passed:
+Failure:
diff --git a/testdata/auth_transfer_limit.tdir/auth_transfer_limit.post b/testdata/auth_transfer_limit.tdir/auth_transfer_limit.post
new file mode 100644 (file)
index 0000000..4a4d834
--- /dev/null
@@ -0,0 +1,14 @@
+# #-- auth_transfer_limit.post --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# source the test var file when it's there
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+#
+# do your teardown here
+. ../common.sh
+kill_pid $FWD_PID
+kill_pid $UNBOUND_PID
+rm -f $CONTROL_PATH/controlpipe.$CONTROL_PID
+echo "> cat logfiles"
+cat fwd.log 
+cat unbound.log
diff --git a/testdata/auth_transfer_limit.tdir/auth_transfer_limit.pre b/testdata/auth_transfer_limit.tdir/auth_transfer_limit.pre
new file mode 100644 (file)
index 0000000..399101f
--- /dev/null
@@ -0,0 +1,42 @@
+# #-- auth_transfer_limit.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 -e "define HAVE_PTHREAD 1" -e "define HAVE_SOLARIS_THREADS 1" -e "define HAVE_WINDOWS_THREADS 1" $PRE/config.h; then
+       TEST_FAST_RELOAD="yes"
+else
+       TEST_FAST_RELOAD="no"
+fi
+echo "TEST_FAST_RELOAD=$TEST_FAST_RELOAD" >> .tpkg.var.test
+
+get_random_port 2
+UNBOUND_PORT=$RND_PORT
+FWD_PORT=$(($RND_PORT + 1))
+echo "UNBOUND_PORT=$UNBOUND_PORT" >> .tpkg.var.test
+echo "FWD_PORT=$FWD_PORT" >> .tpkg.var.test
+
+# start forwarder
+get_ldns_testns
+$LDNS_TESTNS -p $FWD_PORT auth_transfer_limit.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/@TOPORT\@/'$FWD_PORT'/' -e 's?@CONTROL_PATH\@?'$CONTROL_PATH'?' -e 's/@CONTROL_PID@/'$CONTROL_PID'/' < auth_transfer_limit.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/auth_transfer_limit.tdir/auth_transfer_limit.test b/testdata/auth_transfer_limit.tdir/auth_transfer_limit.test
new file mode 100644 (file)
index 0000000..d4a0131
--- /dev/null
@@ -0,0 +1,100 @@
+# #-- auth_transfer_limit.test --#
+# source the master var file when it's there
+[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master
+# use .tpkg.var.test for in test variable passing
+[ -f .tpkg.var.test ] && source .tpkg.var.test
+
+PRE="../.."
+. ../common.sh
+# do the test
+
+teststep "wait for unbound to transfer"
+sleep 3
+
+teststep "check log for max-transfer-size"
+if grep "auth zone example.com. transfer.*exceeded 512 bytes" unbound.log; then
+       echo "OK"
+else
+       echo "Not OK"
+       exit 1
+fi
+
+teststep "check log for max-transfer-time"
+if grep "auth zone example2.com. transfer.*exceeded 2000 msec" unbound.log; then
+       echo "OK"
+else
+       echo "Not OK"
+       exit 1
+fi
+
+if test "$TEST_FAST_RELOAD" == "yes"; then
+       teststep "Testing with fast_reload"
+       cp ub.conf ub.conf.old
+       sed -e 's/max-transfer-size: 512/max-transfer-size: 500/' -e 's/max-transfer-time: 2000/max-transfer-time: 1000/' < ub.conf.old > ub.conf
+
+       teststep "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
+
+       teststep "unbound-control fast_reload +vvdp"
+       $PRE/unbound-control -c ub.conf fast_reload +vvdp 2>&1 | tee output
+       if test $? -ne 0; then
+               echo "wrong exit value."
+               exit 1
+       else
+               echo "exit value: OK"
+       fi
+       wait_logfile unbound.log "start fast reload thread" 60
+       wait_logfile unbound.log "stop fast reload thread" 60
+       wait_logfile unbound.log "joined with fastreload thread" 60
+
+       if grep "ok" output; then
+               echo "OK"
+       else
+               echo "output not correct"
+               exit 1
+       fi
+
+       teststep "wait for unbound to transfer"
+       sleep 3
+
+       $PRE/unbound-control -c ub.conf auth_zone_transfer example.com 2>&1
+       if test $? -ne 0; then
+               echo "wrong exit value."
+               exit 1
+       else
+               echo "exit value: OK"
+       fi
+       $PRE/unbound-control -c ub.conf auth_zone_transfer example2.com 2>&1
+       if test $? -ne 0; then
+               echo "wrong exit value."
+               exit 1
+       else
+               echo "exit value: OK"
+       fi
+       teststep "wait for unbound to transfer"
+       sleep 3
+
+       teststep "check log for max-transfer-size"
+       if grep "auth zone example.com. transfer.*exceeded 500 bytes" unbound.log; then
+               echo "OK"
+       else
+               echo "Not OK"
+               exit 1
+       fi
+
+       teststep "check log for max-transfer-time"
+       if grep "auth zone example2.com. transfer.*exceeded 1000 msec" unbound.log; then
+               echo "OK"
+       else
+               echo "Not OK"
+               exit 1
+        fi
+fi
+
+exit 0
diff --git a/testdata/auth_transfer_limit.tdir/auth_transfer_limit.testns b/testdata/auth_transfer_limit.tdir/auth_transfer_limit.testns
new file mode 100644 (file)
index 0000000..3847d72
--- /dev/null
@@ -0,0 +1,74 @@
+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
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+example.com. IN AXFR
+SECTION ANSWER
+example.com. IN SOA ns.example.com. hostmaster.example.com. 1 3600 900 86400 3600
+example.com.   IN NS   ns.example.net.
+; too big!
+EXTRA_PACKET
+REPLY QR AA NOERROR
+SECTION QUESTION
+example.com. IN AXFR
+SECTION ANSWER
+large01.example.com. IN TXT "123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789"
+large02.example.com. IN TXT "123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789"
+large03.example.com. IN TXT "123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789"
+large04.example.com. IN TXT "123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789"
+large05.example.com. IN TXT "123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789"
+EXTRA_PACKET
+REPLY QR AA NOERROR
+SECTION QUESTION
+example.com. IN AXFR
+SECTION ANSWER
+www.example.com. IN A  1.2.3.4
+example.com. IN SOA ns.example.com. hostmaster.example.com. 1 3600 900 86400 3600
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+example2.com. IN SOA
+SECTION ANSWER
+example2.com. IN SOA ns.example2.com. hostmaster.example2.com. 1 3600 900 86400 3600
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR AA NOERROR
+SECTION QUESTION
+example2.com. IN AXFR
+SECTION ANSWER
+example2.com. IN SOA ns.example2.com. hostmaster.example2.com. 1 3600 900 86400 3600
+example2.com.  IN NS   ns.example2.net.
+EXTRA_PACKET
+REPLY QR AA NOERROR
+; too slow
+ADJUST packet_sleep=3
+SECTION QUESTION
+example2.com. IN AXFR
+SECTION ANSWER
+extra.example2.com. IN A 1.2.3.5
+EXTRA_PACKET
+REPLY QR AA NOERROR
+SECTION QUESTION
+example2.com. IN AXFR
+SECTION ANSWER
+www.example2.com. IN A 1.2.3.4
+example2.com. IN SOA ns.example2.com. hostmaster.example2.com. 1 3600 900 86400 3600
+ENTRY_END
index 6dadf0b72072d9ab7f8e630593188984da93d37e..c2b45759f8e54cc88e2e51e89ab6c0dd7ad10254 100644 (file)
@@ -884,6 +884,10 @@ struct config_auth {
        int zonemd_check;
        /** Reject absence of ZONEMD records, zone must have one */
        int zonemd_reject_absence;
+       /** The maximum auth zone transfer size, in bytes. */
+       size_t max_transfer_size;
+       /** The maximum auth zone transfer time taken, in msec. */
+       int max_transfer_time;
 };
 
 /**
index d6222cdf9a7530d8ec72bc2bd018f58984de6aea..196575bdb8a5e0382a8ffa644ad7938406d758b0 100644 (file)
@@ -608,6 +608,8 @@ iter-scrub-ns{COLON}                { YDVAR(1, VAR_ITER_SCRUB_NS) }
 iter-scrub-cname{COLON}                { YDVAR(1, VAR_ITER_SCRUB_CNAME) }
 iter-scrub-rrsig{COLON}                { YDVAR(1, VAR_ITER_SCRUB_RRSIG) }
 max-global-quota{COLON}                { YDVAR(1, VAR_MAX_GLOBAL_QUOTA) }
+max-transfer-size{COLON}       { YDVAR(1, VAR_MAX_TRANSFER_SIZE) }
+max-transfer-time{COLON}       { YDVAR(1, VAR_MAX_TRANSFER_TIME) }
 iter-scrub-promiscuous{COLON}  { YDVAR(1, VAR_ITER_SCRUB_PROMISCUOUS) }
 <INITIAL,val>{NEWLINE}         { LEXOUT(("NL\n")); cfg_parser->line++; }
 
index 6ea214ea55876ea754da53dee42b2e1468fa2b1f..64a4e589d23adc9d4b24548d13f4ae33cbee3e94 100644 (file)
@@ -216,6 +216,7 @@ extern struct config_parser_state* cfg_parser;
 %token VAR_LOG_DESTADDR VAR_CACHEDB_CHECK_WHEN_SERVE_EXPIRED
 %token VAR_COOKIE_SECRET_FILE VAR_ITER_SCRUB_NS VAR_ITER_SCRUB_CNAME
 %token VAR_ITER_SCRUB_RRSIG
+%token VAR_MAX_TRANSFER_SIZE VAR_MAX_TRANSFER_TIME
 %token VAR_MAX_GLOBAL_QUOTA VAR_HARDEN_UNVERIFIED_GLUE VAR_LOG_TIME_ISO
 %token VAR_ITER_SCRUB_PROMISCUOUS VAR_LOG_THREAD_ID
 
@@ -459,6 +460,8 @@ authstart: VAR_AUTH_ZONE
                        s->zonemd_check = 0;
                        s->zonemd_reject_absence = 0;
                        s->isrpz = 0;
+                       s->max_transfer_size = 0;
+                       s->max_transfer_time = 0;
                } else {
                        yyerror("out of memory");
                }
@@ -468,7 +471,8 @@ contents_auth: contents_auth content_auth
        | ;
 content_auth: auth_name | auth_zonefile | auth_master | auth_url |
        auth_for_downstream | auth_for_upstream | auth_fallback_enabled |
-       auth_allow_notify | auth_zonemd_check | auth_zonemd_reject_absence
+       auth_allow_notify | auth_zonemd_check | auth_zonemd_reject_absence |
+       auth_max_transfer_size | auth_max_transfer_time
        ;
 
 rpz_tag: VAR_TAGS STRING_ARG
@@ -556,6 +560,8 @@ rpzstart: VAR_RPZ
                        s->for_upstream = 0;
                        s->fallback_enabled = 0;
                        s->isrpz = 1;
+                       s->max_transfer_size = 0;
+                       s->max_transfer_time = 0;
                } else {
                        yyerror("out of memory");
                }
@@ -565,7 +571,8 @@ contents_rpz: contents_rpz content_rpz
        | ;
 content_rpz: auth_name | auth_zonefile | rpz_tag | auth_master | auth_url |
           auth_allow_notify | rpz_action_override | rpz_cname_override |
-          rpz_log | rpz_log_name | rpz_signal_nxdomain_ra | auth_for_downstream
+          rpz_log | rpz_log_name | rpz_signal_nxdomain_ra | auth_for_downstream |
+          auth_max_transfer_size | auth_max_transfer_time
        ;
 server_num_threads: VAR_NUM_THREADS STRING_ARG
        {
@@ -3341,6 +3348,23 @@ auth_fallback_enabled: VAR_FALLBACK_ENABLED STRING_ARG
                free($2);
        }
        ;
+auth_max_transfer_size: VAR_MAX_TRANSFER_SIZE STRING_ARG
+       {
+               OUTYY(("P(max-transfer-size:%s)\n", $2));
+               if(!cfg_parse_memsize($2, &cfg_parser->cfg->auths->max_transfer_size))
+                       yyerror("memory size expected");
+               free($2);
+       }
+       ;
+auth_max_transfer_time: VAR_MAX_TRANSFER_TIME STRING_ARG
+       {
+               OUTYY(("P(max-transfer-time:%s)\n", $2));
+               if(atoi($2) == 0 && strcmp($2, "0") != 0)
+                       yyerror("number expected");
+               else cfg_parser->cfg->auths->max_transfer_time = atoi($2);
+               free($2);
+       }
+       ;
 view_name: VAR_NAME STRING_ARG
        {
                OUTYY(("P(name:%s)\n", $2));