return load;
}
+bool ServerPool::hasAtLeastOneServerAvailable() const
+{
+ // NOLINTNEXTLINE(readability-use-anyofallof): no it's not more readable
+ for (const auto& server : d_servers) {
+ if (std::get<1>(server)->isUp()) {
+ return true;
+ }
+ }
+ return false;
+}
+
const ServerPolicy::NumberedServerVector& ServerPool::getServers() const
{
return d_servers;
serv.first = idx++;
}
- if (d_servers.size() == 1) {
- d_tcpOnly = server->isTCPOnly();
- }
- else if (!server->isTCPOnly()) {
- d_tcpOnly = false;
- }
+ updateConsistency();
}
void ServerPool::removeServer(shared_ptr<DownstreamState>& server)
{
size_t idx = 1;
bool found = false;
- bool tcpOnly = true;
for (auto it = d_servers.begin(); it != d_servers.end();) {
if (found) {
- tcpOnly = tcpOnly && it->second->isTCPOnly();
/* we need to renumber the servers placed
after the removed one, for Lua (custom policies) */
it->first = idx++;
it = d_servers.erase(it);
found = true;
} else {
- tcpOnly = tcpOnly && it->second->isTCPOnly();
idx++;
it++;
}
}
+
+ if (found && !d_isConsistent) {
+ updateConsistency();
+ }
+}
+
+void ServerPool::updateConsistency()
+{
+ bool first{true};
+ bool useECS{false};
+ bool tcpOnly{false};
+ bool zeroScope{false};
+
+ for (const auto& serverPair : d_servers) {
+ const auto& server = serverPair.second;
+ if (first) {
+ first = false;
+ useECS = server->d_config.useECS;
+ tcpOnly = server->isTCPOnly();
+ zeroScope = !server->d_config.disableZeroScope;
+ }
+ else {
+ if (server->d_config.useECS != useECS ||
+ server->isTCPOnly() != tcpOnly ||
+ server->d_config.disableZeroScope == zeroScope) {
+ d_tcpOnly = false;
+ d_isConsistent = false;
+ return;
+ }
+ }
+ }
+
d_tcpOnly = tcpOnly;
+ /* at this point we know that all servers agree
+ on these settings, so let's just use the same
+ values for the pool itself */
+ d_useECS = useECS;
+ d_zeroScope = zeroScope;
+ d_isConsistent = true;
+}
+
+void ServerPool::setZeroScope(bool enabled)
+{
+ d_zeroScope = enabled;
+ updateConsistency();
+}
+
+void ServerPool::setECS(bool useECS)
+{
+ d_useECS = useECS;
+ updateConsistency();
}
namespace dnsdist::backend
return d_selected.has_value();
}
- DownstreamState* operator->() const noexcept
+ DownstreamState* operator->() const
{
return (*d_backends)[*d_selected].second.get();
}
- const std::shared_ptr<DownstreamState>& get() const noexcept
+ const std::shared_ptr<DownstreamState>& get() const
{
return (*d_backends)[*d_selected].second;
}
private:
const NumberedServerVector* d_backends{nullptr};
- std::optional<SelectedServerPosition> d_selected;
+ std::optional<SelectedServerPosition> d_selected{std::nullopt};
};
SelectedBackend getSelectedBackend(const ServerPolicy::NumberedServerVector& servers, DNSQuestion& dnsQuestion) const;
});
}
});
+ luaCtx.registerFunction<bool (std::shared_ptr<dnsdist::lua::LuaServerPoolObject>::*)() const>("getZeroScope", [](const std::shared_ptr<dnsdist::lua::LuaServerPoolObject>& pool) {
+ bool zeroScope = false;
+ if (pool) {
+ dnsdist::configuration::updateRuntimeConfiguration([&pool, &zeroScope](dnsdist::configuration::RuntimeConfiguration& config) {
+ auto poolIt = config.d_pools.find(pool->poolName);
+ /* this might happen if the Server Pool has been removed in the meantime, let's gracefully ignore it */
+ if (poolIt != config.d_pools.end()) {
+ zeroScope = poolIt->second.getZeroScope();
+ }
+ });
+ }
+ return zeroScope;
+ });
+ luaCtx.registerFunction<void (std::shared_ptr<dnsdist::lua::LuaServerPoolObject>::*)(bool enabled)>("setZeroScope", [](std::shared_ptr<dnsdist::lua::LuaServerPoolObject>& pool, bool enabled) {
+ if (pool) {
+ dnsdist::configuration::updateRuntimeConfiguration([&pool, enabled](dnsdist::configuration::RuntimeConfiguration& config) {
+ auto poolIt = config.d_pools.find(pool->poolName);
+ if (poolIt == config.d_pools.end()) {
+ /* this might happen if the Server Pool has been removed in the meantime, let's gracefully ignore it */
+ return;
+ }
+ poolIt->second.setZeroScope(enabled);
+ });
+ }
+ });
#ifndef DISABLE_DOWNSTREAM_BINDINGS
/* DownstreamState */
return d_useECS;
}
- void setECS(bool useECS)
+ void setECS(bool useECS);
+
+ bool getZeroScope() const
+ {
+ return d_zeroScope;
+ }
+
+ void setZeroScope(bool enabled);
+
+ bool isConsistent() const
{
- d_useECS = useECS;
+ return d_isConsistent;
}
size_t poolLoad() const;
size_t countServers(bool upOnly) const;
+ bool hasAtLeastOneServerAvailable() const;
const ServerPolicy::NumberedServerVector& getServers() const;
void addServer(std::shared_ptr<DownstreamState>& server);
void removeServer(std::shared_ptr<DownstreamState>& server);
std::shared_ptr<ServerPolicy> policy{nullptr};
private:
+ void updateConsistency();
+
ServerPolicy::NumberedServerVector d_servers;
bool d_useECS{false};
+ bool d_zeroScope{true};
bool d_tcpOnly{false};
+ bool d_isConsistent{true};
};
type: "String"
default: ""
description: "The name of the load-balancing policy associated to this pool. If left empty, the global policy will be used"
+ - name: "use_ecs"
+ type: "bool"
+ default: "false"
+ description: "Whether to 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"
+ - name: "disable_zero_scope"
+ type: "bool"
+ default: "false"
+ description: "Whether to disable the EDNS Client Subnet :doc:`../advanced/zero-scope` feature, which does a cache lookup for an answer valid for all subnets (ECS scope of 0) before adding ECS information to the query and doing the regular lookup, 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, have EDNS Client Subnet enabled and zero scope disabled"
custom_load_balancing_policy:
description: "Settings for a custom load-balancing policy"
if (dnsQuestion.getHeader()->qr) { // something turned it into a response
return handleQueryTurnedIntoSelfAnsweredResponse(dnsQuestion);
}
+ bool backendLookupDone = false;
const auto& serverPool = getPool(dnsQuestion.ids.poolName);
- auto selectedBackend = selectBackendForOutgoingQuery(dnsQuestion, serverPool);
+ ServerPolicy::SelectedBackend selectedBackend(serverPool.getServers());
+ if (!serverPool.packetCache || !serverPool.isConsistent()) {
+ selectedBackend = selectBackendForOutgoingQuery(dnsQuestion, serverPool);
+ backendLookupDone = true;
+ }
+
bool willBeForwardedOverUDP = !dnsQuestion.overTCP() || dnsQuestion.ids.protocol == dnsdist::Protocol::DoH;
if (selectedBackend && selectedBackend->isTCPOnly()) {
willBeForwardedOverUDP = false;
willBeForwardedOverUDP = !serverPool.isTCPOnly();
}
- uint32_t allowExpired = selectedBackend ? 0 : dnsdist::configuration::getCurrentRuntimeConfiguration().d_staleCacheEntriesTTL;
+ uint32_t allowExpired = 0;
+ if (!selectedBackend && dnsdist::configuration::getCurrentRuntimeConfiguration().d_staleCacheEntriesTTL > 0 && (backendLookupDone || !serverPool.hasAtLeastOneServerAvailable())) {
+ allowExpired = dnsdist::configuration::getCurrentRuntimeConfiguration().d_staleCacheEntriesTTL;
+ }
if (serverPool.packetCache && !dnsQuestion.ids.skipCache && !dnsQuestion.ids.dnssecOK) {
dnsQuestion.ids.dnssecOK = (dnsdist::getEDNSZ(dnsQuestion) & EDNS_HEADER_FLAG_DO) != 0;
}
- if (dnsQuestion.useECS && ((selectedBackend && selectedBackend->d_config.useECS) || (!selectedBackend && serverPool.getECS()))) {
+ const bool useECS = dnsQuestion.useECS && ((selectedBackend && selectedBackend->d_config.useECS) || (!selectedBackend && serverPool.getECS()));
+ if (useECS) {
+ const bool useZeroScope = (selectedBackend && !selectedBackend->d_config.disableZeroScope) || (!selectedBackend && serverPool.getZeroScope());
// we special case our cache in case a downstream explicitly gave us a universally valid response with a 0 scope
// we need ECS parsing (parseECS) to be true so we can be sure that the initial incoming query did not have an existing
// ECS option, which would make it unsuitable for the zero-scope feature.
- if (serverPool.packetCache && !dnsQuestion.ids.skipCache && (!selectedBackend || !selectedBackend->d_config.disableZeroScope) && serverPool.packetCache->isECSParsingEnabled()) {
+ if (serverPool.packetCache && !dnsQuestion.ids.skipCache && useZeroScope && serverPool.packetCache->isECSParsingEnabled()) {
if (serverPool.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKeyNoECS, dnsQuestion.ids.subnet, *dnsQuestion.ids.dnssecOK, willBeForwardedOverUDP, allowExpired, false, true, false)) {
vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.protocol.toString(), dnsQuestion.getData().size());
const auto& newServerPool = getPool(dnsQuestion.ids.poolName);
dnsQuestion.ids.packetCache = newServerPool.packetCache;
selectedBackend = selectBackendForOutgoingQuery(dnsQuestion, newServerPool);
+ backendLookupDone = true;
}
else {
dnsQuestion.ids.packetCache = serverPool.packetCache;
}
}
+ if (!backendLookupDone) {
+ selectedBackend = selectBackendForOutgoingQuery(dnsQuestion, serverPool);
+ }
+
if (!selectedBackend) {
auto servFailOnNoPolicy = dnsdist::configuration::getCurrentRuntimeConfiguration().d_servFailOnNoPolicy;
++dnsdist::metrics::g_stats.noPolicy;
Returns the :class:`PacketCache` for this pool or nil.
+ .. method:: ServerPool:getDisableZeroScope()
+
+ .. versionadded:: 2.0.1
+
+ Whether dnsdist will disable the EDNS Client Subnet :doc:`../advanced/zero-scope` feature when looking up into the cache,
+ when all servers from this pool are down.
+
.. method:: ServerPool:getECS()
Whether dnsdist will add EDNS Client Subnet information to the query before looking up into the cache,
:param PacketCache cache: The new cache to add to the pool
- .. method:: ServerPool:unsetCache()
+ .. method:: ServerPool:setDisableZeroScope(disable)
- Removes the cache from this pool.
+ .. versionadded:: 2.0.1
+
+ Set to true if dnsdist should disable the EDNS Client Subnet :doc:`../advanced/zero-scope` feature when looking up into the cache,
+ when all servers from this pool are down.
.. method:: ServerPool:setECS()
and have EDNS Client Subnet enabled, since the queries in the cache will have been inserted with
ECS information. Default is false.
+ .. method:: ServerPool:unsetCache()
+
+ Removes the cache from this pool.
+
PacketCache
~~~~~~~~~~~
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
- _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort']
+ _config_params = ['_consoleKeyB64', '_consolePort', '_testServerPort', '_testServerPort']
_config_template = """
pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
getPool(""):setCache(pc)
setKey("%s")
controlSocket("127.0.0.1:%d")
newServer{address="127.0.0.1:%d", useClientSubnet=true}
+ -- add a second server without ECS, which will never be used
+ -- but makes the pool inconsistent
+ newServer{address="127.0.0.1:%d", useClientSubnet=false}:setDown()
+ getPool(""):setECS(false)
"""
def testCached(self):
_config_template = """
pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
getPool(""):setCache(pc)
- getPool(""):setECS(true)
setKey("%s")
controlSocket("127.0.0.1:%d")
newServer{address="127.0.0.1:%d", useClientSubnet=true}
+ getPool(""):setECS(true)
"""
def testCached(self):