From: Remi Gacogne Date: Tue, 13 Jan 2026 14:26:08 +0000 (+0100) Subject: dnsdist: Test dynamic blocks for DoH clients with X-Forwarded-For, including cache... X-Git-Tag: rec-5.4.0-beta1~43^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dd90f4fc1cf1bd036e961ff36c1773d3cc51c1f3;p=thirdparty%2Fpdns.git dnsdist: Test dynamic blocks for DoH clients with X-Forwarded-For, including cache hits Signed-off-by: Remi Gacogne --- diff --git a/regression-tests.dnsdist/test_DynBlocksRatio.py b/regression-tests.dnsdist/test_DynBlocksRatio.py index 67c6e70506..87bfac9b26 100644 --- a/regression-tests.dnsdist/test_DynBlocksRatio.py +++ b/regression-tests.dnsdist/test_DynBlocksRatio.py @@ -28,6 +28,156 @@ class TestDynBlockGroupServFailsRatio(DynBlocksTest): name = 'servfailratio.group.dynblocks.tests.powerdns.com.' self.doTestRCodeRatio(name, dns.rcode.SERVFAIL, 10, 10) +class TestDynBlockGroupServFailsRatioDoH(DynBlocksTest): + # we need this period to be quite long because we request the valid + # queries to be still looked at to reach the 20 queries count! + _dynBlockPeriod = 6 + _dnsDistListeningAddr = "127.0.0.2" + _serverKey = 'server.key' + _serverCert = 'server.chain' + _serverName = 'tls.tests.dnsdist.org' + _caCert = 'ca.pem' + _dohServerPort = pickAvailablePort() + _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort)) + _webSevrerPort = pickAvailablePort() + _webServerBasicAuthPassword = 'secret' + _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM=' + _webServerAPIKey = 'apisecret' + _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso=' + _config_template = """ + local dbr = dynBlockRulesGroup() + dbr:setRCodeRatio(DNSRCode.SERVFAIL, 0.2, %d, "Exceeded query rate", %d, 20) + + function maintenance() + dbr:apply() + end + + addDOHLocal("%s:%d", "%s", "%s", { "/" }, {trustForwardedForHeader=true}) + setACL({'127.0.0.1', '192.0.2.1/32'}) + + newServer{address="127.0.0.1:%d"} + + webserver("127.0.0.1:%d") + setWebserverConfig({password="%s", apiKey="%s"}) + """ + _config_params = ['_dynBlockPeriod', '_dynBlockDuration', '_dnsDistListeningAddr', '_dohServerPort', '_serverCert', '_serverKey', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] + + def testDynBlocksServFailRatio(self): + """ + Dyn Blocks (group): Server Failure Ratio via DoH + """ + name = 'rcode-servfailratio-doh.group.dynblocks.tests.powerdns.com.' + rcodeQuery = dns.message.make_query(name, 'A', 'IN') + expectedResponse = dns.message.make_response(rcodeQuery) + expectedResponse.set_rcode(dns.rcode.SERVFAIL) + + rcodecount = 20 + sent = 0 + allowed = 0 + for _ in range(rcodecount): + (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, rcodeQuery, response=expectedResponse, caFile=self._caCert, customHeaders=['x-forwarded-for: 192.0.2.1']) + + sent = sent + 1 + if receivedQuery: + receivedQuery.id = rcodeQuery.id + self.assertEqual(rcodeQuery, receivedQuery) + self.assertEqual(expectedResponse, receivedResponse) + allowed = allowed + 1 + else: + # the query has not reached the responder, + # let's clear the response queue + self.clearToResponderQueue() + + # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount + self.assertGreaterEqual(allowed, rcodecount) + + waitForMaintenanceToRun() + + # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod + (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, rcodeQuery, response=None, caFile=self._caCert, customHeaders=['x-forwarded-for: 192.0.2.1'], useQueue=False, timeout=1) + self.assertEqual(receivedResponse, None) + + self.doTestDynBlockViaAPI('192.0.2.1/32', 'Exceeded query rate', 1, self._dynBlockDuration, (sent-allowed)+1, (sent-allowed)+1, False) + +class TestDynBlockGroupServFailsRatioDoHCacheHit(DynBlocksTest): + # we need this period to be quite long because we request the valid + # queries to be still looked at to reach the 20 queries count! + _dynBlockPeriod = 6 + _dnsDistListeningAddr = "127.0.0.2" + _serverKey = 'server.key' + _serverCert = 'server.chain' + _serverName = 'tls.tests.dnsdist.org' + _caCert = 'ca.pem' + _dohServerPort = pickAvailablePort() + _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort)) + _webSevrerPort = pickAvailablePort() + _webServerBasicAuthPassword = 'secret' + _webServerBasicAuthPasswordHashed = '$scrypt$ln=10,p=1,r=8$6DKLnvUYEeXWh3JNOd3iwg==$kSrhdHaRbZ7R74q3lGBqO1xetgxRxhmWzYJ2Qvfm7JM=' + _webServerAPIKey = 'apisecret' + _webServerAPIKeyHashed = '$scrypt$ln=10,p=1,r=8$9v8JxDfzQVyTpBkTbkUqYg==$bDQzAOHeK1G9UvTPypNhrX48w974ZXbFPtRKS34+aso=' + _config_template = """ + local dbr = dynBlockRulesGroup() + dbr:setRCodeRatio(DNSRCode.SERVFAIL, 0.2, %d, "Exceeded query rate", %d, 20) + + function maintenance() + dbr:apply() + end + + addDOHLocal("%s:%d", "%s", "%s", { "/" }, {trustForwardedForHeader=true}) + setACL({'127.0.0.1', '192.0.2.1/32'}) + + pc = newPacketCache(1000, {maxTTL=86400, minTTL=1}) + getPool(""):setCache(pc) + + newServer{address="127.0.0.1:%d"} + + webserver("127.0.0.1:%d") + setWebserverConfig({password="%s", apiKey="%s"}) + """ + _config_params = ['_dynBlockPeriod', '_dynBlockDuration', '_dnsDistListeningAddr', '_dohServerPort', '_serverCert', '_serverKey', '_testServerPort', '_webServerPort', '_webServerBasicAuthPasswordHashed', '_webServerAPIKeyHashed'] + + def testDynBlocksServFailRatio(self): + """ + Dyn Blocks (group): Server Failure Ratio via DoH (cache hits) + """ + name = 'rcode-servfailratio-doh-cache-hits.group.dynblocks.tests.powerdns.com.' + rcodeQuery = dns.message.make_query(name, 'A', 'IN') + expectedResponse = dns.message.make_response(rcodeQuery) + expectedResponse.set_rcode(dns.rcode.SERVFAIL) + + rcodecount = 20 + sent = 0 + allowed = 0 + firstQuery = True + for _ in range(rcodecount): + if firstQuery: + (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, rcodeQuery, response=expectedResponse, caFile=self._caCert, customHeaders=['x-forwarded-for: 192.0.2.1']) + else: + (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, rcodeQuery, response=None, caFile=self._caCert, customHeaders=['x-forwarded-for: 192.0.2.1'], useQueue=False) + + sent = sent + 1 + if receivedQuery: + receivedQuery.id = rcodeQuery.id + self.assertEqual(rcodeQuery, receivedQuery) + self.assertEqual(expectedResponse, receivedResponse) + else: + # the query has not reached the responder, + # let's clear the response queue + self.clearToResponderQueue() + if receivedResponse: + allowed = allowed + 1 + + # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount + self.assertGreaterEqual(allowed, rcodecount) + + waitForMaintenanceToRun() + + # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod + (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, rcodeQuery, response=None, caFile=self._caCert, customHeaders=['x-forwarded-for: 192.0.2.1'], useQueue=False, timeout=1) + self.assertEqual(receivedResponse, None) + + self.doTestDynBlockViaAPI('192.0.2.1/32', 'Exceeded query rate', 1, self._dynBlockDuration, (sent-allowed)+1, (sent-allowed)+1, False) + class TestDynBlockGroupServFailsRatioDoQ(DynBlocksTest): # we need this period to be quite long because we request the valid