summaryrefslogtreecommitdiffstats
path: root/dnsdist.hh
diff options
context:
space:
mode:
Diffstat (limited to 'dnsdist.hh')
-rw-r--r--dnsdist.hh288
1 files changed, 110 insertions, 178 deletions
diff --git a/dnsdist.hh b/dnsdist.hh
index 1f7b4e8..777b27a 100644
--- a/dnsdist.hh
+++ b/dnsdist.hh
@@ -20,6 +20,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
+
#include "config.h"
#include "ext/luawrapper/include/LuaContext.hpp"
@@ -42,7 +43,9 @@
#include "dnsdist-lbpolicies.hh"
#include "dnsdist-protocols.hh"
#include "dnsname.hh"
-#include "doh.hh"
+#include "dnsdist-doh-common.hh"
+#include "doq.hh"
+#include "doh3.hh"
#include "ednsoptions.hh"
#include "iputils.hh"
#include "misc.hh"
@@ -87,20 +90,26 @@ struct DNSQuestion
return data;
}
- dnsheader* getHeader()
+ bool editHeader(const std::function<bool(dnsheader&)>& editFunction);
+
+ const dnsheader_aligned getHeader() const
{
if (data.size() < sizeof(dnsheader)) {
throw std::runtime_error("Trying to access the dnsheader of a too small (" + std::to_string(data.size()) + ") DNSQuestion buffer");
}
- return reinterpret_cast<dnsheader*>(&data.at(0));
+ dnsheader_aligned dh(data.data());
+ return dh;
}
- const dnsheader* getHeader() const
+ /* this function is not safe against unaligned access, you should
+ use editHeader() instead, but we need it for the Lua bindings */
+ dnsheader* getMutableHeader() const
{
if (data.size() < sizeof(dnsheader)) {
throw std::runtime_error("Trying to access the dnsheader of a too small (" + std::to_string(data.size()) + ") DNSQuestion buffer");
}
- return reinterpret_cast<const dnsheader*>(&data.at(0));
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+ return reinterpret_cast<dnsheader*>(data.data());
}
bool hasRoomFor(size_t more) const
@@ -140,6 +149,13 @@ struct DNSQuestion
ids.qTag->insert_or_assign(key, value);
}
+ void setTag(const std::string& key, std::string&& value) {
+ if (!ids.qTag) {
+ ids.qTag = std::make_unique<QTag>();
+ }
+ ids.qTag->insert_or_assign(key, std::move(value));
+ }
+
const struct timespec& getQueryRealTime() const
{
return ids.queryRealTime.d_start;
@@ -258,7 +274,7 @@ public:
class DNSResponseAction
{
public:
- enum class Action : uint8_t { Allow, Delay, Drop, HeaderModify, ServFail, None };
+ enum class Action : uint8_t { Allow, Delay, Drop, HeaderModify, ServFail, Truncate, None };
virtual Action operator()(DNSResponse*, string* ruleresult) const =0;
virtual ~DNSResponseAction()
{
@@ -330,156 +346,6 @@ extern vector<pair<struct timeval, std::string> > g_confDelta;
using pdns::stat_t;
-struct DNSDistStats
-{
- stat_t responses{0};
- stat_t servfailResponses{0};
- stat_t queries{0};
- stat_t frontendNXDomain{0};
- stat_t frontendServFail{0};
- stat_t frontendNoError{0};
- stat_t nonCompliantQueries{0};
- stat_t nonCompliantResponses{0};
- stat_t rdQueries{0};
- stat_t emptyQueries{0};
- stat_t aclDrops{0};
- stat_t dynBlocked{0};
- stat_t ruleDrop{0};
- stat_t ruleNXDomain{0};
- stat_t ruleRefused{0};
- stat_t ruleServFail{0};
- stat_t ruleTruncated{0};
- stat_t selfAnswered{0};
- stat_t downstreamTimeouts{0};
- stat_t downstreamSendErrors{0};
- stat_t truncFail{0};
- stat_t noPolicy{0};
- stat_t cacheHits{0};
- stat_t cacheMisses{0};
- stat_t latency0_1{0}, latency1_10{0}, latency10_50{0}, latency50_100{0}, latency100_1000{0}, latencySlow{0}, latencySum{0}, latencyCount{0};
- stat_t securityStatus{0};
- stat_t dohQueryPipeFull{0};
- stat_t dohResponsePipeFull{0};
- stat_t outgoingDoHQueryPipeFull{0};
- stat_t proxyProtocolInvalid{0};
- stat_t tcpQueryPipeFull{0};
- stat_t tcpCrossProtocolQueryPipeFull{0};
- stat_t tcpCrossProtocolResponsePipeFull{0};
- double latencyAvg100{0}, latencyAvg1000{0}, latencyAvg10000{0}, latencyAvg1000000{0};
- double latencyTCPAvg100{0}, latencyTCPAvg1000{0}, latencyTCPAvg10000{0}, latencyTCPAvg1000000{0};
- double latencyDoTAvg100{0}, latencyDoTAvg1000{0}, latencyDoTAvg10000{0}, latencyDoTAvg1000000{0};
- double latencyDoHAvg100{0}, latencyDoHAvg1000{0}, latencyDoHAvg10000{0}, latencyDoHAvg1000000{0};
- using statfunction_t = std::function<uint64_t(const std::string&)>;
- using entry_t = boost::variant<stat_t*, pdns::stat_t_trait<double>*, double*, statfunction_t>;
- struct EntryPair
- {
- std::string d_name;
- entry_t d_value;
- };
-
- SharedLockGuarded<std::vector<EntryPair>> entries{std::vector<EntryPair>{
- {"responses", &responses},
- {"servfail-responses", &servfailResponses},
- {"queries", &queries},
- {"frontend-nxdomain", &frontendNXDomain},
- {"frontend-servfail", &frontendServFail},
- {"frontend-noerror", &frontendNoError},
- {"acl-drops", &aclDrops},
- {"rule-drop", &ruleDrop},
- {"rule-nxdomain", &ruleNXDomain},
- {"rule-refused", &ruleRefused},
- {"rule-servfail", &ruleServFail},
- {"rule-truncated", &ruleTruncated},
- {"self-answered", &selfAnswered},
- {"downstream-timeouts", &downstreamTimeouts},
- {"downstream-send-errors", &downstreamSendErrors},
- {"trunc-failures", &truncFail},
- {"no-policy", &noPolicy},
- {"latency0-1", &latency0_1},
- {"latency1-10", &latency1_10},
- {"latency10-50", &latency10_50},
- {"latency50-100", &latency50_100},
- {"latency100-1000", &latency100_1000},
- {"latency-slow", &latencySlow},
- {"latency-avg100", &latencyAvg100},
- {"latency-avg1000", &latencyAvg1000},
- {"latency-avg10000", &latencyAvg10000},
- {"latency-avg1000000", &latencyAvg1000000},
- {"latency-tcp-avg100", &latencyTCPAvg100},
- {"latency-tcp-avg1000", &latencyTCPAvg1000},
- {"latency-tcp-avg10000", &latencyTCPAvg10000},
- {"latency-tcp-avg1000000", &latencyTCPAvg1000000},
- {"latency-dot-avg100", &latencyDoTAvg100},
- {"latency-dot-avg1000", &latencyDoTAvg1000},
- {"latency-dot-avg10000", &latencyDoTAvg10000},
- {"latency-dot-avg1000000", &latencyDoTAvg1000000},
- {"latency-doh-avg100", &latencyDoHAvg100},
- {"latency-doh-avg1000", &latencyDoHAvg1000},
- {"latency-doh-avg10000", &latencyDoHAvg10000},
- {"latency-doh-avg1000000", &latencyDoHAvg1000000},
- {"uptime", uptimeOfProcess},
- {"real-memory-usage", getRealMemoryUsage},
- {"special-memory-usage", getSpecialMemoryUsage},
- {"udp-in-errors", std::bind(udpErrorStats, "udp-in-errors")},
- {"udp-noport-errors", std::bind(udpErrorStats, "udp-noport-errors")},
- {"udp-recvbuf-errors", std::bind(udpErrorStats, "udp-recvbuf-errors")},
- {"udp-sndbuf-errors", std::bind(udpErrorStats, "udp-sndbuf-errors")},
- {"udp-in-csum-errors", std::bind(udpErrorStats, "udp-in-csum-errors")},
- {"udp6-in-errors", std::bind(udp6ErrorStats, "udp6-in-errors")},
- {"udp6-recvbuf-errors", std::bind(udp6ErrorStats, "udp6-recvbuf-errors")},
- {"udp6-sndbuf-errors", std::bind(udp6ErrorStats, "udp6-sndbuf-errors")},
- {"udp6-noport-errors", std::bind(udp6ErrorStats, "udp6-noport-errors")},
- {"udp6-in-csum-errors", std::bind(udp6ErrorStats, "udp6-in-csum-errors")},
- {"tcp-listen-overflows", std::bind(tcpErrorStats, "ListenOverflows")},
- {"noncompliant-queries", &nonCompliantQueries},
- {"noncompliant-responses", &nonCompliantResponses},
- {"proxy-protocol-invalid", &proxyProtocolInvalid},
- {"rdqueries", &rdQueries},
- {"empty-queries", &emptyQueries},
- {"cache-hits", &cacheHits},
- {"cache-misses", &cacheMisses},
- {"cpu-iowait", getCPUIOWait},
- {"cpu-steal", getCPUSteal},
- {"cpu-sys-msec", getCPUTimeSystem},
- {"cpu-user-msec", getCPUTimeUser},
- {"fd-usage", getOpenFileDescriptors},
- {"dyn-blocked", &dynBlocked},
- {"dyn-block-nmg-size", [](const std::string&) { return g_dynblockNMG.getLocal()->size(); }},
- {"security-status", &securityStatus},
- {"doh-query-pipe-full", &dohQueryPipeFull},
- {"doh-response-pipe-full", &dohResponsePipeFull},
- {"outgoing-doh-query-pipe-full", &outgoingDoHQueryPipeFull},
- {"tcp-query-pipe-full", &tcpQueryPipeFull},
- {"tcp-cross-protocol-query-pipe-full", &tcpCrossProtocolQueryPipeFull},
- {"tcp-cross-protocol-response-pipe-full", &tcpCrossProtocolResponsePipeFull},
- // Latency histogram
- {"latency-sum", &latencySum},
- {"latency-count", &latencyCount},
- }};
- struct MutableCounter
- {
- MutableCounter() = default;
- MutableCounter(MutableCounter&& rhs): d_value(rhs.d_value.load())
- {
- }
-
- mutable stat_t d_value{0};
- };
- struct MutableGauge
- {
- MutableGauge() = default;
- MutableGauge(MutableGauge&& rhs): d_value(rhs.d_value.load())
- {
- }
-
- mutable pdns::stat_t_trait<double> d_value{0};
- };
- SharedLockGuarded<std::map<std::string, MutableCounter, std::less<>>> customCounters;
- SharedLockGuarded<std::map<std::string, MutableGauge, std::less<>>> customGauges;
-};
-
-extern struct DNSDistStats g_stats;
-
class BasicQPSLimiter
{
public:
@@ -605,9 +471,13 @@ struct QueryCount {
extern QueryCount g_qcount;
+class XskPacket;
+class XskSocket;
+class XskWorker;
+
struct ClientState
{
- ClientState(const ComboAddress& local_, bool isTCP_, bool doReusePort, int fastOpenQueue, const std::string& itfName, const std::set<int>& cpus_): cpus(cpus_), interface(itfName), local(local_), fastOpenQueueSize(fastOpenQueue), tcp(isTCP_), reuseport(doReusePort)
+ ClientState(const ComboAddress& local_, bool isTCP_, bool doReusePort, int fastOpenQueue, const std::string& itfName, const std::set<int>& cpus_, bool enableProxyProtocol): cpus(cpus_), interface(itfName), local(local_), fastOpenQueueSize(fastOpenQueue), tcp(isTCP_), reuseport(doReusePort), d_enableProxyProtocol(enableProxyProtocol)
{
}
@@ -642,7 +512,10 @@ struct ClientState
std::shared_ptr<DNSCryptContext> dnscryptCtx{nullptr};
std::shared_ptr<TLSFrontend> tlsFrontend{nullptr};
std::shared_ptr<DOHFrontend> dohFrontend{nullptr};
+ std::shared_ptr<DOQFrontend> doqFrontend{nullptr};
+ std::shared_ptr<DOH3Frontend> doh3Frontend{nullptr};
std::shared_ptr<BPFFilter> d_filter{nullptr};
+ std::shared_ptr<XskWorker> xskInfo{nullptr};
size_t d_maxInFlightQueriesPerConn{1};
size_t d_tcpConcurrentConnectionsLimit{0};
int udpFD{-1};
@@ -652,6 +525,7 @@ struct ClientState
bool muted{false};
bool tcp;
bool reuseport;
+ bool d_enableProxyProtocol{true}; // the global proxy protocol ACL still applies
bool ready{false};
int getSocket() const
@@ -679,6 +553,17 @@ struct ClientState
return tlsFrontend != nullptr || (dohFrontend != nullptr && dohFrontend->isHTTPS());
}
+ const TLSFrontend& getTLSFrontend() const
+ {
+ if (tlsFrontend != nullptr) {
+ return *tlsFrontend;
+ }
+ if (dohFrontend) {
+ return dohFrontend->d_tlsContext;
+ }
+ throw std::runtime_error("Trying to get a TLS frontend from a non-TLS ClientState");
+ }
+
dnsdist::Protocol getProtocol() const
{
if (dnscryptCtx) {
@@ -693,6 +578,12 @@ struct ClientState
else if (hasTLS()) {
return dnsdist::Protocol::DoT;
}
+ else if (doqFrontend != nullptr) {
+ return dnsdist::Protocol::DoQ;
+ }
+ else if (doh3Frontend != nullptr) {
+ return dnsdist::Protocol::DoH3;
+ }
else if (udpFD != -1) {
return dnsdist::Protocol::DoUDP;
}
@@ -705,7 +596,13 @@ struct ClientState
{
std::string result = udpFD != -1 ? "UDP" : "TCP";
- if (dohFrontend) {
+ if (doqFrontend) {
+ result += " (DNS over QUIC)";
+ }
+ else if (doh3Frontend) {
+ result += " (DNS over HTTP/3)";
+ }
+ else if (dohFrontend) {
if (dohFrontend->isHTTPS()) {
result += " (DNS over HTTPS)";
}
@@ -731,7 +628,7 @@ struct ClientState
}
}
- void attachFilter(shared_ptr<BPFFilter> bpf, int socket)
+ void attachFilter(shared_ptr<BPFFilter>& bpf, int socket)
{
detachFilter(socket);
@@ -754,7 +651,7 @@ struct ClientState
}
}
- void attachFilter(shared_ptr<BPFFilter> bpf)
+ void attachFilter(shared_ptr<BPFFilter>& bpf)
{
detachFilter();
@@ -810,6 +707,10 @@ struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
std::string d_dohPath;
std::string name;
std::string nameWithAddr;
+#ifdef HAVE_XSK
+ std::array<uint8_t, 6> sourceMACAddr;
+ std::array<uint8_t, 6> destMACAddr;
+#endif /* HAVE_XSK */
size_t d_numberOfSockets{1};
size_t d_maxInFlightQueriesPerConn{1};
size_t d_tcpConcurrentConnectionsLimit{0};
@@ -839,6 +740,7 @@ struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
bool mustResolve{false};
bool useECS{false};
bool useProxyProtocol{false};
+ bool d_proxyProtocolAdvertiseTLS{false};
bool setCD{false};
bool disableZeroScope{false};
bool tcpFastOpen{false};
@@ -851,6 +753,16 @@ struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
bool d_upgradeToLazyHealthChecks{false};
};
+ struct HealthCheckMetrics
+ {
+ stat_t d_failures{0};
+ stat_t d_timeOuts{0};
+ stat_t d_parseErrors{0};
+ stat_t d_networkErrors{0};
+ stat_t d_mismatchErrors{0};
+ stat_t d_invalidResponseErrors{0};
+ };
+
DownstreamState(DownstreamState::Config&& config, std::shared_ptr<TLSCtx> tlsCtx, bool connect);
DownstreamState(const ComboAddress& remote): DownstreamState(DownstreamState::Config(remote), nullptr, false)
{
@@ -859,6 +771,7 @@ struct DownstreamState: public std::enable_shared_from_this<DownstreamState>
~DownstreamState();
Config d_config;
+ HealthCheckMetrics d_healthCheckMetrics;
stat_t sendErrors{0};
stat_t outstanding{0};
stat_t reuseds{0};
@@ -911,27 +824,46 @@ public:
std::vector<int> sockets;
StopWatch sw;
QPSLimiter qps;
+#ifdef HAVE_XSK
+ std::vector<std::shared_ptr<XskWorker>> d_xskInfos;
+ std::vector<std::shared_ptr<XskSocket>> d_xskSockets;
+#endif
std::atomic<uint64_t> idOffset{0};
size_t socketsOffset{0};
double latencyUsec{0.0};
double latencyUsecTCP{0.0};
unsigned int d_nextCheck{0};
uint16_t currentCheckFailures{0};
- uint8_t consecutiveSuccessfulChecks{0};
std::atomic<bool> hashesComputed{false};
std::atomic<bool> connected{false};
bool upStatus{false};
private:
+ void handleUDPTimeout(IDState& ids);
+ void updateNextLazyHealthCheck(LazyHealthCheckStats& stats, bool checkScheduled, std::optional<time_t> currentTime = std::nullopt);
void connectUDPSockets();
+#ifdef HAVE_XSK
+ void addXSKDestination(int fd);
+ void removeXSKDestination(int fd);
+#endif /* HAVE_XSK */
- std::thread tid;
std::mutex connectLock;
std::condition_variable d_connectedWait;
+#ifdef HAVE_XSK
+ SharedLockGuarded<std::vector<ComboAddress>> d_socketSourceAddresses;
+#endif
std::atomic_flag threadStarted;
+ uint8_t consecutiveSuccessfulChecks{0};
bool d_stopped{false};
public:
-
+ void updateStatisticsInfo()
+ {
+ auto delta = sw.udiffAndSet() / 1000000.0;
+ queryLoad.store(1.0 * (queries.load() - prev.queries.load()) / delta);
+ dropRate.store(1.0 * (reuseds.load() - prev.reuseds.load()) / delta);
+ prev.queries.store(queries.load());
+ prev.reuseds.store(reuseds.load());
+ }
void start();
bool isUp() const
@@ -1054,12 +986,17 @@ public:
void handleUDPTimeouts();
void reportTimeoutOrError();
void reportResponse(uint8_t rcode);
- void submitHealthCheckResult(bool initial, bool newState);
+ void submitHealthCheckResult(bool initial, bool newResult);
time_t getNextLazyHealthCheck();
uint16_t saveState(InternalQueryState&&);
void restoreState(uint16_t id, InternalQueryState&&);
std::optional<InternalQueryState> getState(uint16_t id);
+#ifdef HAVE_XSK
+ void registerXsk(std::vector<std::shared_ptr<XskSocket>>& xsks);
+ [[nodiscard]] ComboAddress pickSourceAddressForSending();
+#endif /* HAVE_XSK */
+
dnsdist::Protocol getProtocol() const
{
if (isDoH()) {
@@ -1085,9 +1022,6 @@ public:
static int s_udpTimeout;
static bool s_randomizeSockets;
static bool s_randomizeIDs;
-private:
- void handleUDPTimeout(IDState& ids);
- void updateNextLazyHealthCheck(LazyHealthCheckStats& stats, bool checkScheduled, std::optional<time_t> currentTime = std::nullopt);
};
using servers_t = vector<std::shared_ptr<DownstreamState>>;
@@ -1101,7 +1035,7 @@ public:
virtual ~DNSRule ()
{
}
- virtual bool matches(const DNSQuestion* dq) const =0;
+ virtual bool matches(const DNSQuestion* dq) const = 0;
virtual string toString() const = 0;
mutable stat_t d_matches{0};
};
@@ -1182,6 +1116,8 @@ extern ComboAddress g_serverControl; // not changed during runtime
extern std::vector<shared_ptr<TLSFrontend>> g_tlslocals;
extern std::vector<shared_ptr<DOHFrontend>> g_dohlocals;
+extern std::vector<shared_ptr<DOQFrontend>> g_doqlocals;
+extern std::vector<shared_ptr<DOH3Frontend>> g_doh3locals;
extern std::vector<std::unique_ptr<ClientState>> g_frontends;
extern bool g_truncateTC;
extern bool g_fixupCase;
@@ -1226,20 +1162,16 @@ struct LocalHolders
LocalStateHolder<pools_t> pools;
};
-void tcpAcceptorThread(std::vector<ClientState*> states);
-
-#ifdef HAVE_DNS_OVER_HTTPS
-void dohThread(ClientState* cs);
-#endif /* HAVE_DNS_OVER_HTTPS */
+void tcpAcceptorThread(const std::vector<ClientState*>& states);
void setLuaNoSideEffect(); // if nothing has been declared, set that there are no side effects
void setLuaSideEffect(); // set to report a side effect, cancelling all _no_ side effect calls
bool getLuaNoSideEffect(); // set if there were only explicit declarations of _no_ side effect
void resetLuaSideEffect(); // reset to indeterminate state
-bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote, unsigned int& qnameWireLength);
+bool responseContentMatches(const PacketBuffer& response, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const std::shared_ptr<DownstreamState>& remote);
-bool checkQueryHeaders(const struct dnsheader* dh, ClientState& cs);
+bool checkQueryHeaders(const struct dnsheader& dnsHeader, ClientState& clientState);
extern std::vector<std::shared_ptr<DNSCryptContext>> g_dnsCryptLocals;
int handleDNSCryptQuery(PacketBuffer& packet, DNSCryptQuery& query, bool tcp, time_t now, PacketBuffer& response);
@@ -1254,7 +1186,6 @@ extern bool g_addEDNSToSelfGeneratedResponses;
extern std::set<std::string> g_capabilitiesToRetain;
static const uint16_t s_udpIncomingBufferSize{1500}; // don't accept UDP queries larger than this value
-static const size_t s_maxPacketCacheEntrySize{4096}; // don't cache responses larger than this value
enum class ProcessQueryResult : uint8_t { Drop, SendAnswer, PassToBackend, Asynchronous };
ProcessQueryResult processQuery(DNSQuestion& dq, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend);
@@ -1262,10 +1193,11 @@ ProcessQueryResult processQueryAfterRules(DNSQuestion& dq, LocalHolders& holders
bool processResponse(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& respRuleActions, const std::vector<DNSDistResponseRuleAction>& insertedRespRuleActions, DNSResponse& dr, bool muted);
bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::string& ruleresult, bool& drop);
bool processResponseAfterRules(PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, DNSResponse& dr, bool muted);
+bool processResponderPacket(std::shared_ptr<DownstreamState>& dss, PacketBuffer& response, const std::vector<DNSDistResponseRuleAction>& localRespRuleActions, const std::vector<DNSDistResponseRuleAction>& cacheInsertedRespRuleActions, InternalQueryState&& ids);
-bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& ds, uint16_t queryID, DNSQuestion& dq, PacketBuffer& query, ComboAddress& dest);
+bool assignOutgoingUDPQueryToBackend(std::shared_ptr<DownstreamState>& downstream, uint16_t queryID, DNSQuestion& dnsQuestion, PacketBuffer& query, bool actuallySend = true);
-ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& ss, const int sd, const PacketBuffer& request, bool healthCheck = false);
+ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& backend, const int socketDesc, const PacketBuffer& request, bool healthCheck = false);
bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote);
void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, dnsdist::Protocol incomingProtocol, bool fromBackend);
void handleResponseSent(const InternalQueryState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, bool fromBackend);