From: Remi Gacogne Date: Fri, 2 Dec 2016 14:05:36 +0000 (+0100) Subject: dnsdist: Add an option to return ServFail when no server is available X-Git-Tag: dnsdist-1.1.0-beta2~19^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F4726%2Fhead;p=thirdparty%2Fpdns.git dnsdist: Add an option to return ServFail when no server is available --- diff --git a/pdns/README-dnsdist.md b/pdns/README-dnsdist.md index 1f8455e9fb..73e1ec87e3 100644 --- a/pdns/README-dnsdist.md +++ b/pdns/README-dnsdist.md @@ -1385,6 +1385,7 @@ instantiate a server with additional parameters * `setServerPolicyLua(name, function)`: set server selection policy to one named 'name' and provided by 'function' * `showServerPolicy()`: show name of currently operational server selection policy * `newServerPolicy(name, function)`: create a policy object from a Lua function + * `setServFailWhenNoServer(bool)`: if set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query * Available policies: * `firstAvailable`: Pick first server that has not exceeded its QPS limit, ordered by the server 'order' parameter * `whashed`: Weighted hashed ('sticky') distribution over available servers, based on the server 'weight' parameter diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index 437c4b5525..65221eebc8 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -339,6 +339,7 @@ const std::vector g_consoleKeywords{ { "setRules", true, "list of rules", "replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see `newRuleAction()`)" }, { "setServerPolicy", true, "policy", "set server selection policy to that policy" }, { "setServerPolicyLua", true, "name, function", "set server selection policy to one named 'name' and provided by 'function'" }, + { "setServFailWhenNoServer", true, "bool", "if set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query" }, { "setTCPRecvTimeout", true, "n", "set the read timeout on TCP connections from the client, in seconds" }, { "setTCPSendTimeout", true, "n", "set the write timeout on TCP connections from the client, in seconds" }, { "setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged" }, diff --git a/pdns/dnsdist-lua2.cc b/pdns/dnsdist-lua2.cc index 87330d33b1..8acc670da1 100644 --- a/pdns/dnsdist-lua2.cc +++ b/pdns/dnsdist-lua2.cc @@ -1117,4 +1117,9 @@ void moreLua(bool client) } } }); + + g_lua.writeFunction("setServFailWhenNoServer", [](bool servfail) { + setLuaSideEffect(); + g_servFailOnNoPolicy = servfail; + }); } diff --git a/pdns/dnsdist-tcp.cc b/pdns/dnsdist-tcp.cc index 9d9f956872..9dc758c7db 100644 --- a/pdns/dnsdist-tcp.cc +++ b/pdns/dnsdist-tcp.cc @@ -333,10 +333,24 @@ void* tcpClientThread(int pipefd) g_stats.cacheMisses++; } - if(!ds) { - g_stats.noPolicy++; - break; - } + if(!ds) { + g_stats.noPolicy++; + + if (g_servFailOnNoPolicy) { + restoreFlags(dh, origFlags); + dq.dh->rcode = RCode::ServFail; + dq.dh->qr = true; + +#ifdef HAVE_DNSCRYPT + if (!encryptResponse(queryBuffer, &dq.len, dq.size, true, dnsCryptQuery)) { + goto drop; + } +#endif + sendResponseToClient(ci.fd, query, dq.len); + } + + break; + } int dsock = -1; if(sockets.count(ds->remote) == 0) { diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 113967adb6..747bdcdfa1 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -134,6 +134,7 @@ DNSAction::Action g_dynBlockAction = DNSAction::Action::Drop; int g_tcpRecvTimeout{2}; int g_tcpSendTimeout{2}; +bool g_servFailOnNoPolicy{false}; bool g_truncateTC{1}; bool g_fixupCase{0}; static void truncateTC(const char* packet, uint16_t* len) @@ -1112,7 +1113,23 @@ try if(!ss) { g_stats.noPolicy++; - continue; + + if (g_servFailOnNoPolicy) { + char* response = query; + uint16_t responseLen = dq.len; + restoreFlags(dh, origFlags); + + dq.dh->rcode = RCode::ServFail; + dq.dh->qr = true; + +#ifdef HAVE_DNSCRYPT + if (!encryptResponse(response, &responseLen, dq.size, false, dnsCryptQuery)) { + continue; + } +#endif + sendUDPResponse(cs->udpFD, response, responseLen, 0, dest, remote); + } + continue; } ss->queries++; diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index 4ada81837c..306537cd00 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -692,6 +692,7 @@ extern bool g_verboseHealthChecks; extern uint32_t g_staleCacheEntriesTTL; extern bool g_apiReadWrite; extern std::string g_apiConfigDirectory; +extern bool g_servFailOnNoPolicy; struct ConsoleKeyword { std::string name; diff --git a/regression-tests.dnsdist/test_Routing.py b/regression-tests.dnsdist/test_Routing.py index 807cdea4e6..f26ba41162 100644 --- a/regression-tests.dnsdist/test_Routing.py +++ b/regression-tests.dnsdist/test_Routing.py @@ -357,7 +357,31 @@ class TestRoutingOrder(DNSDistTest): self.assertEquals(response, receivedResponse) total = 0 - self.assertEquals(self._responsesCounter['UDP Responder'], 0) + if 'UDP Responder' in self._responsesCounter: + self.assertEquals(self._responsesCounter['UDP Responder'], 0) self.assertEquals(self._responsesCounter['UDP Responder 2'], numberOfQueries) - self.assertEquals(self._responsesCounter['TCP Responder'], 0) + if 'TCP Responder' in self._responsesCounter: + self.assertEquals(self._responsesCounter['TCP Responder'], 0) self.assertEquals(self._responsesCounter['TCP Responder 2'], numberOfQueries) + +class TestRoutingNoServer(DNSDistTest): + + _config_template = """ + newServer{address="127.0.0.1:%s", pool="real"} + setServFailWhenNoServer(true) + """ + + def testPolicyPoolNoServer(self): + """ + Routing: No server should return ServFail + """ + name = 'noserver.routing.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + expectedResponse = dns.message.make_response(query) + expectedResponse.set_rcode(dns.rcode.SERVFAIL) + + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertEquals(receivedResponse, expectedResponse) + + (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) + self.assertEquals(receivedResponse, expectedResponse)