luaCtx.writeFunction("setTCPSendTimeout", [](int timeout) { g_tcpSendTimeout = timeout; });
- luaCtx.writeFunction("setUDPTimeout", [](int timeout) { g_udpTimeout = timeout; });
+ luaCtx.writeFunction("setUDPTimeout", [](int timeout) { DownstreamState::s_udpTimeout = timeout; });
luaCtx.writeFunction("setMaxUDPOutstanding", [](uint64_t max) {
if (!g_configurationDone) {
GlobalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange>> g_dynblockNMG;
GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
DNSAction::Action g_dynBlockAction = DNSAction::Action::Drop;
-int g_udpTimeout{2};
bool g_servFailOnNoPolicy{false};
bool g_truncateTC{false};
dnsheader* dh = reinterpret_cast<struct dnsheader*>(response.data());
queryId = dh->id;
- if (queryId >= dss->idStates.size()) {
+ IDState* ids = dss->getExistingState(queryId);
+ if (ids == nullptr) {
continue;
}
- IDState* ids = &dss->idStates[queryId];
int64_t usageIndicator = ids->usageIndicator;
if (!IDState::isInUse(usageIndicator)) {
vinfolog("Got answer from %s, relayed to %s, took %f usec", dss->remote.toStringWithPort(), ids->origRemote.toStringWithPort(), udiff);
handleResponseSent(*ids, udiff, *dr.remote, dss->remote, static_cast<unsigned int>(got), cleartextDH, dss->getProtocol());
+ dss->releaseState(queryId);
dss->latencyUsec = (127.0 * dss->latencyUsec / 128.0) + udiff/128.0;
dss->prev.queries.store(dss->queries.load());
dss->prev.reuseds.store(dss->reuseds.load());
- for (IDState& ids : dss->idStates) { // timeouts
- int64_t usageIndicator = ids.usageIndicator;
- if(IDState::isInUse(usageIndicator) && ids.age++ > g_udpTimeout) {
- /* We mark the state as unused as soon as possible
- to limit the risk of racing with the
- responder thread.
- */
- auto oldDU = ids.du;
-
- if (!ids.tryMarkUnused(usageIndicator)) {
- /* this state has been altered in the meantime,
- don't go anywhere near it */
- continue;
- }
- ids.du = nullptr;
- handleDOHTimeout(DOHUnitUniquePtr(oldDU, DOHUnit::release));
- oldDU = nullptr;
- ids.age = 0;
- dss->reuseds++;
- --dss->outstanding;
- ++g_stats.downstreamTimeouts; // this is an 'actively' discovered timeout
- vinfolog("Had a downstream timeout from %s (%s) for query for %s|%s from %s",
- dss->remote.toStringWithPort(), dss->getName(),
- ids.qname.toLogString(), QType(ids.qtype).toString(), ids.origRemote.toStringWithPort());
-
- struct timespec ts;
- gettime(&ts);
-
- struct dnsheader fake;
- memset(&fake, 0, sizeof(fake));
- fake.id = ids.origID;
-
- g_rings.insertResponse(ts, ids.origRemote, ids.qname, ids.qtype, std::numeric_limits<unsigned int>::max(), 0, fake, dss->remote, dss->getProtocol());
- }
- }
+ dss->handleTimeouts();
}
handleQueuedHealthChecks(*mplexer);
private:
std::string name;
std::string nameWithAddr;
+ LockGuarded<std::map<uint16_t, IDState>> d_idStatesMap;
+ vector<IDState> idStates;
public:
std::shared_ptr<TLSCtx> d_tlsCtx{nullptr};
std::vector<int> sockets;
- vector<IDState> idStates;
set<string> pools;
std::mutex connectLock;
std::thread tid;
bool passCrossProtocolQuery(std::unique_ptr<CrossProtocolQuery>&& cpq);
int pickSocketForSending();
void pickSocketsReadyForReceiving(std::vector<int>& ready);
+ void handleTimeouts();
IDState* getIDState(unsigned int& id, int64_t& generation);
+ IDState* getExistingState(unsigned int id);
+ void releaseState(unsigned int id);
dnsdist::Protocol getProtocol() const
{
return dnsdist::Protocol::DoUDP;
}
+ static int s_udpTimeout;
static bool s_randomizeSockets;
static bool s_randomizeIDs;
+private:
+ void handleTimeout(IDState& ids);
};
using servers_t =vector<std::shared_ptr<DownstreamState>>;
extern bool g_fixupCase;
extern int g_tcpRecvTimeout;
extern int g_tcpSendTimeout;
-extern int g_udpTimeout;
extern uint16_t g_maxOutstanding;
extern std::atomic<bool> g_configurationDone;
extern boost::optional<uint64_t> g_maxTCPClientThreads;
#include "dnsdist.hh"
#include "dnsdist-nghttp2.hh"
#include "dnsdist-random.hh"
+#include "dnsdist-rings.hh"
#include "dnsdist-tcp.hh"
#include "dolog.hh"
void DownstreamState::connectUDPSockets(size_t numberOfSockets)
{
- idStates.resize(g_maxOutstanding);
+ if (s_randomizeIDs) {
+ idStates.clear();
+ }
+ else {
+ idStates.resize(g_maxOutstanding);
+ }
sockets.resize(numberOfSockets);
if (sockets.size() > 1) {
bool DownstreamState::s_randomizeSockets{false};
bool DownstreamState::s_randomizeIDs{false};
+int DownstreamState::s_udpTimeout{2};
+
+static bool isIDSExpired(IDState& ids)
+{
+ auto age = ids.age++;
+ return age > DownstreamState::s_udpTimeout;
+}
+
+void DownstreamState::handleTimeout(IDState& ids)
+{
+ /* We mark the state as unused as soon as possible
+ to limit the risk of racing with the
+ responder thread.
+ */
+ auto oldDU = ids.du;
+
+ ids.du = nullptr;
+ handleDOHTimeout(DOHUnitUniquePtr(oldDU, DOHUnit::release));
+ oldDU = nullptr;
+ ids.age = 0;
+ reuseds++;
+ --outstanding;
+ ++g_stats.downstreamTimeouts; // this is an 'actively' discovered timeout
+ vinfolog("Had a downstream timeout from %s (%s) for query for %s|%s from %s",
+ remote.toStringWithPort(), getName(),
+ ids.qname.toLogString(), QType(ids.qtype).toString(), ids.origRemote.toStringWithPort());
+
+ struct timespec ts;
+ gettime(&ts);
+
+ struct dnsheader fake;
+ memset(&fake, 0, sizeof(fake));
+ fake.id = ids.origID;
+
+ g_rings.insertResponse(ts, ids.origRemote, ids.qname, ids.qtype, std::numeric_limits<unsigned int>::max(), 0, fake, remote, getProtocol());
+}
+
+void DownstreamState::handleTimeouts()
+{
+ if (s_randomizeIDs) {
+ auto map = d_idStatesMap.lock();
+ for (auto it = map->begin(); it != map->end(); ) {
+ auto& ids = it->second;
+ if (isIDSExpired(ids)) {
+ handleTimeout(ids);
+ it = map->erase(it);
+ continue;
+ }
+ ++it;
+ }
+ }
+ else {
+ for (IDState& ids : idStates) {
+ int64_t usageIndicator = ids.usageIndicator;
+ if (IDState::isInUse(usageIndicator) && isIDSExpired(ids)) {
+ if (!ids.tryMarkUnused(usageIndicator)) {
+ /* this state has been altered in the meantime,
+ don't go anywhere near it */
+ continue;
+ }
+
+ handleTimeout(ids);
+ }
+ }
+ }
+}
+
+IDState* DownstreamState::getExistingState(unsigned int stateId)
+{
+ if (s_randomizeIDs) {
+ auto map = d_idStatesMap.lock();
+ auto it = map->find(stateId);
+ if (it == map->end()) {
+ return nullptr;
+ }
+ return &it->second;
+ }
+ else {
+ if (stateId >= idStates.size()) {
+ return nullptr;
+ }
+ return &idStates[stateId];
+ }
+}
+
+void DownstreamState::releaseState(unsigned int stateId)
+{
+ if (s_randomizeIDs) {
+ auto map = d_idStatesMap.lock();
+ auto it = map->find(stateId);
+ if (it == map->end()) {
+ return;
+ }
+ if (it->second.isInUse()) {
+ return;
+ }
+ map->erase(it);
+ }
+}
IDState* DownstreamState::getIDState(unsigned int& selectedID, int64_t& generation)
{
up to 5 five times. The last selected one is used
even if it was already in use */
size_t remainingAttempts = 5;
+ auto map = d_idStatesMap.lock();
+
+ bool done = false;
do {
- selectedID = dnsdist::getRandomValue(idStates.size());
- ids = &idStates[selectedID];
- remainingAttempts--;
+ selectedID = dnsdist::getRandomValue(std::numeric_limits<uint16_t>::max());
+ auto [it, inserted] = map->insert({selectedID, IDState()});
+ ids = &it->second;
+ if (inserted) {
+ done = true;
+ }
+ else {
+ remainingAttempts--;
+ }
}
- while (ids->isInUse() && remainingAttempts > 0);
+ while (!done && remainingAttempts > 0);
}
else {
selectedID = (idOffset++) % idStates.size();
.. versionadded:: 1.8.0
- Setting this parameter to true (default is false) will randomize the IDs in outgoing UDP queries, at a small performance cost. :func:`setMaxUDPOutstanding`
- should be set at its highest possible value (default since 1.4.0) to make that setting fully efficient. This is only useful if the path between dnsdist
- and the backend is not trusted and the 'TCP-only', DNS over TLS or DNS over HTTPS transports cannot be used.
+ Setting this parameter to true (default is false) will randomize the IDs in outgoing UDP queries, at a small performance cost, ignoring the :func:`setMaxUDPOutstanding`
+ value. This is only useful if the path between dnsdist and the backend is not trusted and the 'TCP-only', DNS over TLS or DNS over HTTPS transports cannot be used.
See also :func:`setRandomizedOutgoingSockets`.
.. function:: setRandomizedOutgoingSockets(val):