From 7e68774418431980ab5fe70bba6b4b437a0502cd Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Tue, 27 Mar 2018 10:39:16 +0200 Subject: [PATCH] dnsdist: Optionally add ECS for cache lookup when all backends are down --- pdns/dnsdist-lua-bindings.cc | 2 + pdns/dnsdist-tcp.cc | 2 +- pdns/dnsdist.cc | 2 +- pdns/dnsdist.hh | 11 ++ pdns/dnsdistdist/docs/reference/config.rst | 17 +++ regression-tests.dnsdist/test_Caching.py | 125 +++++++++++++++++++++ 6 files changed, 157 insertions(+), 2 deletions(-) diff --git a/pdns/dnsdist-lua-bindings.cc b/pdns/dnsdist-lua-bindings.cc index 24e31590af..bac46f77f7 100644 --- a/pdns/dnsdist-lua-bindings.cc +++ b/pdns/dnsdist-lua-bindings.cc @@ -68,6 +68,8 @@ void setupLuaBindings(bool client) pool->packetCache = nullptr; } }); + g_lua.registerFunction("getECS", &ServerPool::getECS); + g_lua.registerFunction("setECS", &ServerPool::setECS); /* DownstreamState */ g_lua.registerFunction("setQPS", [](DownstreamState& s, int lim) { s.qps = lim ? QPSLimiter(lim, lim) : QPSLimiter(); }); diff --git a/pdns/dnsdist-tcp.cc b/pdns/dnsdist-tcp.cc index 3e38084a9c..f0ec210d4c 100644 --- a/pdns/dnsdist-tcp.cc +++ b/pdns/dnsdist-tcp.cc @@ -401,7 +401,7 @@ void* tcpClientThread(int pipefd) ds = policy.policy(servers, &dq); } - if (dq.useECS && ds && ds->useECS) { + if (dq.useECS && ((ds && ds->useECS) || (!ds && serverPool->getECS()))) { uint16_t newLen = dq.len; if (!handleEDNSClientSubnet(query, dq.size, consumed, &newLen, &ednsAdded, &ecsAdded, ci.remote, dq.ecsOverride, dq.ecsPrefixLength)) { vinfolog("Dropping query from %s because we couldn't insert the ECS value", ci.remote.toStringWithPort()); diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index fe665e5134..0107f33ba8 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -1348,7 +1348,7 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct bool ednsAdded = false; bool ecsAdded = false; - if (dq.useECS && ss && ss->useECS) { + if (dq.useECS && ((ss && ss->useECS) || (!ss && serverPool->getECS()))) { if (!handleEDNSClientSubnet(query, dq.size, consumed, &dq.len, &(ednsAdded), &(ecsAdded), remote, dq.ecsOverride, dq.ecsPrefixLength)) { vinfolog("Dropping query from %s because we couldn't insert the ECS value", remote.toStringWithPort()); return; diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index 862efa60b5..b2d7faee0b 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -657,6 +657,16 @@ struct ServerPool const std::shared_ptr getCache() const { return packetCache; }; + bool getECS() const + { + return d_useECS; + } + + void setECS(bool useECS) + { + d_useECS = useECS; + } + std::shared_ptr packetCache{nullptr}; std::shared_ptr policy{nullptr}; @@ -723,6 +733,7 @@ struct ServerPool private: NumberedVector> d_servers; pthread_rwlock_t d_lock; + bool d_useECS{false}; }; using pools_t=map>; void setPoolPolicy(pools_t& pools, const string& poolName, std::shared_ptr policy); diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index b136c5ceaa..046241ce30 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -421,6 +421,13 @@ Pools are automatically created when a server is added to a pool (with :func:`ne Returns the :class:`PacketCache` for this pool or nil. + .. method:: ServerPool:getECS() + + .. versionadded:: 1.3.0 + + Whether dnsdist will add EDNS Client Subnet information to the query before looking up into the cache, + when all servers from this pool are down. For more information see :meth:`ServerPool:setECS`. + .. method:: ServerPool:setCache(cache) Adds ``cache`` as the pool's cache. @@ -431,6 +438,16 @@ Pools are automatically created when a server is added to a pool (with :func:`ne Removes the cache from this pool. + .. method:: ServerPool:setECS() + + .. versionadded:: 1.3.0 + + Set to true if dnsdist should add EDNS Client Subnet information to the query before looking up into the cache, + when all servers from this pool are down. If at least one server is up, the preference of the + selected server is used, this parameter is only useful if all the backends in this pool are down + and have EDNS Client Subnet enabled, since the queries in the cache will have been inserted with + ECS information. Default is false. + PacketCache ~~~~~~~~~~~ diff --git a/regression-tests.dnsdist/test_Caching.py b/regression-tests.dnsdist/test_Caching.py index c11c10d74c..2d6214fbc6 100644 --- a/regression-tests.dnsdist/test_Caching.py +++ b/regression-tests.dnsdist/test_Caching.py @@ -1372,3 +1372,128 @@ class TestCachingDontAge(DNSDistTest): total += self._responsesCounter[key] self.assertEquals(total, misses) + +class TestCachingECSWithoutPoolECS(DNSDistTest): + + _consoleKey = DNSDistTest.generateConsoleKey() + _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') + _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort'] + _config_template = """ + pc = newPacketCache(100, 86400, 1) + getPool(""):setCache(pc) + setKey("%s") + controlSocket("127.0.0.1:%d") + newServer{address="127.0.0.1:%d", useClientSubnet=true} + """ + + def testCached(self): + """ + Cache: Cached entry with ECS is a miss when no backend are available + """ + ttl = 600 + name = 'cached.cache-ecs-without-pool-ecs.tests.powerdns.com.' + query = dns.message.make_query(name, 'AAAA', 'IN') + response = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + ttl, + dns.rdataclass.IN, + dns.rdatatype.AAAA, + '::1') + response.answer.append(rrset) + + # first query to fill the cache + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, response) + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, response) + + # next queries should hit the cache + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertEquals(receivedResponse, response) + + # over TCP too + (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) + self.assertEquals(receivedResponse, response) + + # we mark the backend as down + self.sendConsoleCommand("getServer(0):setDown()") + + # we should NOT get a cached entry since it has ECS and we haven't asked the pool + # to add ECS when no backend is up + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertEquals(receivedResponse, None) + + # same over TCP + (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) + self.assertEquals(receivedResponse, None) + +class TestCachingECSWithPoolECS(DNSDistTest): + + _consoleKey = DNSDistTest.generateConsoleKey() + _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') + _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort'] + _config_template = """ + pc = newPacketCache(100, 86400, 1) + getPool(""):setCache(pc) + getPool(""):setECS(true) + setKey("%s") + controlSocket("127.0.0.1:%d") + newServer{address="127.0.0.1:%d", useClientSubnet=true} + """ + + def testCached(self): + """ + Cache: Cached entry with ECS is a hit when no backend are available + """ + ttl = 600 + name = 'cached.cache-ecs-with-pool-ecs.tests.powerdns.com.' + query = dns.message.make_query(name, 'AAAA', 'IN') + response = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + ttl, + dns.rdataclass.IN, + dns.rdatatype.AAAA, + '::1') + response.answer.append(rrset) + + # first query to fill the cache + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, response) + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, response) + + # next queries should hit the cache + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertEquals(receivedResponse, response) + + # over TCP too + (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) + self.assertEquals(receivedResponse, response) + + # we mark the backend as down + self.sendConsoleCommand("getServer(0):setDown()") + + # we should STILL get a cached entry since it has ECS and we have asked the pool + # to add ECS when no backend is up + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertEquals(receivedResponse, response) + + # same over TCP + (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) + self.assertEquals(receivedResponse, response) -- 2.47.2