]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Optionally add ECS for cache lookup when all backends are down 6400/head
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 27 Mar 2018 08:39:16 +0000 (10:39 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 27 Mar 2018 08:39:16 +0000 (10:39 +0200)
pdns/dnsdist-lua-bindings.cc
pdns/dnsdist-tcp.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/docs/reference/config.rst
regression-tests.dnsdist/test_Caching.py

index 24e31590af56d9d81020df624d33435fd4ce13ad..bac46f77f7a7da0bf10d6d2ad47f55ffd1d07e8f 100644 (file)
@@ -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<void(DownstreamState::*)(int)>("setQPS", [](DownstreamState& s, int lim) { s.qps = lim ? QPSLimiter(lim, lim) : QPSLimiter(); });
index 3e38084a9cf566ee916edb6d0e11e96ad1eaa061..f0ec210d4c1475f8851e6fa94038f476991bf134 100644 (file)
@@ -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());
index fe665e5134ae2b69751b863d30835ae9bc5bd76f..0107f33ba878bef797fe931beadbbfd46fc30335 100644 (file)
@@ -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;
index 862efa60b5ac51a3890bbd5f7f1f16c3bb87d8bf..b2d7faee0b44be370cbe9375c9dc1298d76ca9b4 100644 (file)
@@ -657,6 +657,16 @@ struct ServerPool
 
   const std::shared_ptr<DNSDistPacketCache> getCache() const { return packetCache; };
 
+  bool getECS() const
+  {
+    return d_useECS;
+  }
+
+  void setECS(bool useECS)
+  {
+    d_useECS = useECS;
+  }
+
   std::shared_ptr<DNSDistPacketCache> packetCache{nullptr};
   std::shared_ptr<ServerPolicy> policy{nullptr};
 
@@ -723,6 +733,7 @@ struct ServerPool
 private:
   NumberedVector<shared_ptr<DownstreamState>> d_servers;
   pthread_rwlock_t d_lock;
+  bool d_useECS{false};
 };
 using pools_t=map<std::string,std::shared_ptr<ServerPool>>;
 void setPoolPolicy(pools_t& pools, const string& poolName, std::shared_ptr<ServerPolicy> policy);
index b136c5ceaa9fb66c106f20d7c0ca518388fc8a03..046241ce30ff960907cf1d3167b6786b34647d48 100644 (file)
@@ -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
 ~~~~~~~~~~~
 
index c11c10d74cd807668a8788b66205de1e9382da48..2d6214fbc60f5eb0281b0efd6b1f075d3a0f4463 100644 (file)
@@ -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)