]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add xfr quota starvation system test
authorAlessio Podda <alessio@isc.org>
Mon, 13 Apr 2026 13:55:38 +0000 (15:55 +0200)
committerMichał Kępień <michal@isc.org>
Thu, 7 May 2026 11:32:15 +0000 (13:32 +0200)
Add a starvation test that tries to starve the XFR quota with
unautorized requests.

bin/tests/system/xferquota/ns1/named.conf.j2
bin/tests/system/xferquota/ns3/named.conf.j2 [new file with mode: 0644]
bin/tests/system/xferquota/ns3/quota.db [new file with mode: 0644]
bin/tests/system/xferquota/ns3/root.db [new file with mode: 0644]
bin/tests/system/xferquota/tests_xferquota.py

index 9bba994dd423892bda409029e1a9ac3eb7583907..90e9417c42c3a8d8736f013aca337b0000b18f41 100644 (file)
@@ -10,6 +10,8 @@ options {
        recursion no;
        dnssec-validation no;
        notify yes;
+
+       transfers-out 3;
 };
 
 key rndc_key {
diff --git a/bin/tests/system/xferquota/ns3/named.conf.j2 b/bin/tests/system/xferquota/ns3/named.conf.j2
new file mode 100644 (file)
index 0000000..4a0d70c
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0.  If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+       query-source address 10.53.0.3;
+       notify-source 10.53.0.3;
+       transfer-source 10.53.0.3;
+       port @PORT@;
+       pid-file "named.pid";
+       listen-on { 10.53.0.3; };
+       listen-on-v6 { none; };
+       recursion no;
+       dnssec-validation no;
+
+       transfers-out 1;
+       allow-transfer { 10.53.0.2; };
+};
+
+key rndc_key {
+       secret "1234abcd8765";
+       algorithm @DEFAULT_HMAC@;
+};
+
+controls {
+       inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+zone "." {
+       type primary;
+       file "root.db";
+};
+
+zone "quota." {
+       type primary;
+       file "quota.db";
+};
diff --git a/bin/tests/system/xferquota/ns3/quota.db b/bin/tests/system/xferquota/ns3/quota.db
new file mode 100644 (file)
index 0000000..12a67d3
--- /dev/null
@@ -0,0 +1,22 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0.  If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 300
+@      IN SOA  ns1.quota. hostmaster.quota. (
+                       1               ; serial
+                       3600            ; refresh
+                       1800            ; retry
+                       604800          ; expire
+                       600             ; minimum
+                       )
+       IN NS   ns1.quota.
+ns1    IN A    10.53.0.3
+www    IN A    10.0.0.1
diff --git a/bin/tests/system/xferquota/ns3/root.db b/bin/tests/system/xferquota/ns3/root.db
new file mode 100644 (file)
index 0000000..a5ff0fc
--- /dev/null
@@ -0,0 +1,21 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0.  If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 300
+.                      IN SOA  ns.root. hostmaster.root. (
+                               1               ; serial
+                               3600            ; refresh
+                               1800            ; retry
+                               604800          ; expire
+                               600             ; minimum
+                               )
+.                      NS      a.root-servers.nil.
+a.root-servers.nil.    A       10.53.0.3
index 6d255085cb558638bab65579a746d4e837f3d2bb..5d394d070ebe6a9bdc9358865daaf29edf9f8388 100644 (file)
 from re import compile as Re
 
 import glob
+import multiprocessing
 import os
 import re
 import shutil
 import signal
 import time
 
+import dns.message
+import dns.query
 import dns.zone
 import pytest
 
@@ -60,6 +63,9 @@ def test_xferquota(named_port, ns1, ns2):
                     matching_line_count += 1
         return matching_line_count == 300
 
+    # The primary has 'transfers-out 3;', while the secondary has
+    # 'transfers-in 5; transfer-per-ns 5;'. This will allow all the zones
+    # to be eventually transferred, hitting the quotas now and then.
     isctest.run.retry_with_timeout(check_line_count, timeout=360)
 
     axfr_msg = isctest.query.create("zone000099.example.", "AXFR")
@@ -80,3 +86,39 @@ def test_xferquota(named_port, ns1, ns2):
     with ns2.watch_log_from_start(timeout=30) as watcher:
         watcher.wait_for_line(pattern)
     query_and_compare(a_msg)
+
+
+def _flood_unauthorized_axfrs(port, duration):
+    """Child process: send unauthorized AXFR requests for `duration` seconds."""
+    deadline = time.monotonic() + duration
+    while time.monotonic() < deadline:
+        try:
+            msg = dns.message.make_query("quota.", "AXFR")
+            dns.query.tcp(msg, "10.53.0.3", port=port, timeout=2, source="10.53.0.1")
+        except Exception:  # pylint: disable=broad-exception-caught
+            pass
+
+
+def test_xfrquota_unauthorized_no_starve(named_port):
+    """Unauthorized AXFR clients must not consume XFR-out quota (GL #3859).
+
+    ns3 is configured with transfers-out 1 and allow-transfer { 10.53.0.2; }.
+    We flood AXFR requests from unauthorized source processes (10.53.0.1) and
+    verify that an authorized client (10.53.0.2) can still transfer.
+    """
+    with multiprocessing.Pool(10) as pool:
+        pool.starmap_async(_flood_unauthorized_axfrs, [(named_port, 5)] * 10)
+
+        # Give the flood a moment to saturate
+        time.sleep(1)
+
+        # Try an authorized AXFR from 10.53.0.2 multiple times to increase
+        # the chance of hitting the race window where quota is consumed.
+        zone = dns.zone.Zone("quota.")
+        dns.query.inbound_xfr(
+            "10.53.0.3",
+            zone,
+            port=named_port,
+            timeout=10,
+            source="10.53.0.2",
+        )