• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 19741624072

27 Nov 2025 03:45PM UTC coverage: 73.086% (+0.02%) from 73.065%
19741624072

Pull #16570

github

web-flow
Merge 08a2cdb1d into f94a3f63f
Pull Request #16570: rec: rewrite all unwrap calls in web.rs

38523 of 63408 branches covered (60.75%)

Branch coverage included in aggregate %.

128044 of 164496 relevant lines covered (77.84%)

6531485.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.28
/pdns/dnsdistdist/dnsdist-lbpolicies.cc
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
 */
22

23
#include "dnsdist.hh"
24
#include "dnsdist-lbpolicies.hh"
25
#include "dnsdist-lua.hh"
26
#include "dnsdist-lua-ffi.hh"
27
#include "dolog.hh"
28
#include "dns_random.hh"
29

30
static constexpr size_t s_staticArrayCutOff = 16;
31
template <typename T> using DynamicIndexArray = std::vector<std::pair<T, size_t>>;
32
template <typename T> using StaticIndexArray = std::array<std::pair<T, size_t>, s_staticArrayCutOff>;
33

34
static std::optional<ServerPolicy::SelectedServerPosition> getLeastOutstanding(const ServerPolicy::NumberedServerVector& servers)
35
{
29✔
36
  std::optional<ServerPolicy::SelectedServerPosition> best;
29✔
37
  uint64_t leastOutstandingSeen = std::numeric_limits<uint64_t>::max();
29✔
38
  int lowestOrderSeen = std::numeric_limits<int>::max();
29✔
39
  double lowestLatencySeen = std::numeric_limits<double>::max();
29✔
40

41
  for (const auto& server : servers) {
29✔
42
    if (!server.second->isUp()) {
22✔
43
      continue;
14✔
44
    }
14✔
45

46
    auto outstanding = server.second->outstanding.load();
8✔
47
    auto order = server.second->d_config.order;
8✔
48
    if (outstanding > leastOutstandingSeen) {
8!
49
      continue;
×
50
    }
×
51
    if (outstanding < leastOutstandingSeen) {
8✔
52
      best = server.first;
6✔
53
      leastOutstandingSeen = outstanding;
6✔
54
      lowestOrderSeen = order;
6✔
55
      lowestLatencySeen = server.second->getRelevantLatencyUsec();
6✔
56
      continue;
6✔
57
    }
6✔
58
    if (order > lowestOrderSeen) {
2!
59
      continue;
×
60
    }
×
61
    auto latency = server.second->getRelevantLatencyUsec();
2✔
62
    if (latency < lowestLatencySeen) {
2✔
63
      best = server.first;
1✔
64
      leastOutstandingSeen = outstanding;
1✔
65
      lowestOrderSeen = order;
1✔
66
      lowestLatencySeen = server.second->getRelevantLatencyUsec();
1✔
67
    }
1✔
68
  }
2✔
69

70
  return best;
29✔
71
}
29✔
72

73
// get server with least outstanding queries, and within those, with the lowest order, and within those: the fastest
74
std::optional<ServerPolicy::SelectedServerPosition> leastOutstanding(const ServerPolicy::NumberedServerVector& servers, [[maybe_unused]] const DNSQuestion* dnsQuestion)
75
{
3,844✔
76
  if (servers.size() == 1 && servers[0].second->isUp()) {
3,844✔
77
    return 1;
3,815✔
78
  }
3,815✔
79

80
  return getLeastOutstanding(servers);
29✔
81
}
3,844✔
82

83
std::optional<ServerPolicy::SelectedServerPosition> firstAvailable(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dnsQuestion)
84
{
104✔
85
  for (const auto& server : servers) {
104!
86
    if (server.second->isUp() && (!server.second->d_qpsLimiter || server.second->d_qpsLimiter->checkOnly())) {
104!
87
      return server.first;
104✔
88
    }
104✔
89
  }
104✔
90
  return leastOutstanding(servers, dnsQuestion);
×
91
}
104✔
92

93
template <class T> static std::optional<ServerPolicy::SelectedServerPosition> getValRandom(const ServerPolicy::NumberedServerVector& servers, T& poss, const unsigned int val, const double targetLoad)
94
{
1,100✔
95
  constexpr int max = std::numeric_limits<int>::max();
1,100✔
96
  int sum = 0;
1,100✔
97

98
  size_t usableServers = 0;
1,100✔
99
  const auto weightedBalancingFactor = dnsdist::configuration::getImmutableConfiguration().d_weightedBalancingFactor;
1,100✔
100
  for (const auto& server : servers) {      // w=1, w=10 -> 1, 11
2,000!
101
    if (server.second->isUp() && (weightedBalancingFactor == 0 || (static_cast<double>(server.second->outstanding.load()) <= (targetLoad * server.second->d_config.d_weight)))) {
2,000!
102
      // Don't overflow sum when adding high weights
103
      if (server.second->d_config.d_weight > max - sum) {
2,000!
104
        sum = max;
200✔
105
      } else {
1,800✔
106
        sum += server.second->d_config.d_weight;
1,800✔
107
      }
1,800✔
108

109
      poss.at(usableServers) = std::pair(sum, server.first);
2,000✔
110
      usableServers++;
2,000✔
111
    }
2,000✔
112
  }
2,000✔
113

114
  // Catch the case where usableServers or sum are equal to 0 to avoid a SIGFPE
115
  if (usableServers == 0 || sum == 0) {
1,100!
116
    return std::nullopt;
×
117
  }
×
118

119
  int randomVal = static_cast<int>(val % sum);
1,100✔
120
  auto selected = std::upper_bound(poss.begin(), poss.begin() + usableServers, randomVal, [](int randomVal_, const typename T::value_type& serverPair) { return  randomVal_ < serverPair.first;});
2,000✔
121
  if (selected == poss.begin() + usableServers) {
1,100!
122
    return std::nullopt;
×
123
  }
×
124

125
  return selected->second;
1,100✔
126
}
1,100✔
127

128
static std::optional<ServerPolicy::SelectedServerPosition> valrandom(const unsigned int val, const ServerPolicy::NumberedServerVector& servers)
129
{
1,100✔
130
  using ValRandomType = int;
1,100✔
131
  double targetLoad = std::numeric_limits<double>::max();
1,100✔
132
  const auto weightedBalancingFactor = dnsdist::configuration::getImmutableConfiguration().d_weightedBalancingFactor;
1,100✔
133
  if (weightedBalancingFactor > 0) {
1,100!
134
    /* we start with one, representing the query we are currently handling */
135
    double currentLoad = 1;
×
136
    size_t totalWeight = 0;
×
137
    for (const auto& pair : servers) {
×
138
      if (pair.second->isUp()) {
×
139
        currentLoad += pair.second->outstanding;
×
140
        totalWeight += pair.second->d_config.d_weight;
×
141
      }
×
142
    }
×
143

144
    if (totalWeight > 0) {
×
145
      targetLoad = (currentLoad / static_cast<double>(totalWeight)) * weightedBalancingFactor;
×
146
    }
×
147
  }
×
148

149
  if (servers.size() <= s_staticArrayCutOff) {
1,100!
150
    StaticIndexArray<ValRandomType> poss;
1,100✔
151
    return getValRandom(servers, poss, val, targetLoad);
1,100✔
152
  }
1,100✔
153

154
  DynamicIndexArray<ValRandomType> poss;
×
155
  poss.resize(servers.size());
×
156
  return getValRandom(servers, poss, val, targetLoad);
×
157
}
1,100✔
158

159
std::optional<ServerPolicy::SelectedServerPosition> wrandom(const ServerPolicy::NumberedServerVector& servers, [[maybe_unused]] const DNSQuestion* dnsQuestion)
160
{
900✔
161
  return valrandom(dns_random_uint32(), servers);
900✔
162
}
900✔
163

164
std::optional<ServerPolicy::SelectedServerPosition> whashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t hash)
165
{
200✔
166
  return valrandom(hash, servers);
200✔
167
}
200✔
168

169
std::optional<ServerPolicy::SelectedServerPosition> whashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dnsQuestion)
170
{
200✔
171
  const auto hashPerturbation = dnsdist::configuration::getImmutableConfiguration().d_hashPerturbation;
200✔
172
  return whashedFromHash(servers, dnsQuestion->ids.qname.hash(hashPerturbation));
200✔
173
}
200✔
174

175
std::optional<ServerPolicy::SelectedServerPosition> chashedFromHash(const ServerPolicy::NumberedServerVector& servers, size_t qhash)
176
{
200✔
177
  unsigned int sel = std::numeric_limits<unsigned int>::max();
200✔
178
  unsigned int min = std::numeric_limits<unsigned int>::max();
200✔
179
  std::optional<ServerPolicy::SelectedServerPosition> ret;
200✔
180
  std::optional<ServerPolicy::SelectedServerPosition> first;
200✔
181

182
  double targetLoad = std::numeric_limits<double>::max();
200✔
183
  const auto consistentHashBalancingFactor = dnsdist::configuration::getImmutableConfiguration().d_consistentHashBalancingFactor;
200✔
184
  if (consistentHashBalancingFactor > 0) {
200!
185
    /* we start with one, representing the query we are currently handling */
186
    double currentLoad = 1;
×
187
    size_t totalWeight = 0;
×
188
    for (const auto& pair : servers) {
×
189
      if (pair.second->isUp()) {
×
190
        currentLoad += pair.second->outstanding;
×
191
        totalWeight += pair.second->d_config.d_weight;
×
192
      }
×
193
    }
×
194

195
    if (totalWeight > 0) {
×
196
      targetLoad = (currentLoad / static_cast<double>(totalWeight)) * consistentHashBalancingFactor;
×
197
    }
×
198
  }
×
199

200
  for (const auto& serverPair: servers) {
400✔
201
    if (serverPair.second->isUp() && (consistentHashBalancingFactor == 0 || static_cast<double>(serverPair.second->outstanding.load()) <= (targetLoad * serverPair.second->d_config.d_weight))) {
400!
202
      // make sure hashes have been computed
203
      if (!serverPair.second->hashesComputed) {
400!
204
        serverPair.second->hash();
×
205
      }
×
206
      {
400✔
207
        const auto position = serverPair.first;
400✔
208
        const auto& server = serverPair.second;
400✔
209
        auto hashes = server->hashes.read_lock();
400✔
210
        // we want to keep track of the last hash
211
        if (min > *(hashes->begin())) {
400!
212
          min = *(hashes->begin());
400✔
213
          first = position;
400✔
214
        }
400✔
215

216
        auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
400✔
217
        if (hash_it != hashes->end()) {
400✔
218
          if (*hash_it < sel) {
398✔
219
            sel = *hash_it;
311✔
220
            ret = position;
311✔
221
          }
311✔
222
        }
398✔
223
      }
400✔
224
    }
400✔
225
  }
400✔
226
  if (ret) {
200✔
227
    return ret;
199✔
228
  }
199✔
229
  if (first) {
1!
230
    return first;
1✔
231
  }
1✔
232
  return std::nullopt;
×
233
}
1✔
234

235
std::optional<ServerPolicy::SelectedServerPosition> chashed(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dnsQuestion)
236
{
200✔
237
  const auto hashPerturbation = dnsdist::configuration::getImmutableConfiguration().d_hashPerturbation;
200✔
238
  return chashedFromHash(servers, dnsQuestion->ids.qname.hash(hashPerturbation));
200✔
239
}
200✔
240

241
std::optional<ServerPolicy::SelectedServerPosition> roundrobin(const ServerPolicy::NumberedServerVector& servers, [[maybe_unused]] const DNSQuestion* dnsQuestion)
242
{
62✔
243
  if (servers.empty()) {
62!
244
    return std::nullopt;
×
245
  }
×
246

247
  static std::atomic<unsigned int> counter{0};
62✔
248

249
  size_t serverIdx = (counter++) % servers.size();
62✔
250
  auto currentServer = servers.at(serverIdx);
62✔
251
  if (currentServer.second->isUp()) {
62✔
252
    return currentServer.first;
50✔
253
  }
50✔
254

255
  vector<size_t> candidates;
12✔
256
  candidates.reserve(servers.size());
12✔
257

258
  for (const auto& server : servers) {
24✔
259
    if (server.second->isUp()) {
24✔
260
      candidates.push_back(server.first);
10✔
261
    }
10✔
262
  }
24✔
263

264
  if (candidates.empty()) {
12✔
265
    if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_roundrobinFailOnNoServer) {
2!
266
      return std::nullopt;
2✔
267
    }
2✔
268
    for (const auto& server : servers) {
×
269
      candidates.push_back(server.first);
×
270
    }
×
271
  }
×
272

273
  return candidates.at(counter % candidates.size());
10✔
274
}
12✔
275

276
std::optional<ServerPolicy::SelectedServerPosition> orderedWrandUntag(const ServerPolicy::NumberedServerVector& servers, const DNSQuestion* dnsQuestion)
277
{
500✔
278
  if (servers.empty()) {
500!
279
    return std::nullopt;
×
280
  }
×
281

282
  ServerPolicy::NumberedServerVector candidates;
500✔
283
  candidates.reserve(servers.size());
500✔
284
  std::vector<ServerPolicy::SelectedServerPosition> positionsMap;
500✔
285
  positionsMap.reserve(servers.size());
500✔
286

287
  int curOrder = std::numeric_limits<int>::max();
500✔
288
  unsigned int curNumber = 1;
500✔
289

290
  for (const auto& svr : servers) {
1,700✔
291
    if (svr.second->isUp() && (!dnsQuestion->ids.qTag || dnsQuestion->ids.qTag->count(svr.second->getNameWithAddr()) == 0)) {
1,700✔
292
      // the servers in a pool are already sorted in ascending order by its 'order', see ``ServerPool::addServer()``
293
      if (svr.second->d_config.order > curOrder) {
1,100✔
294
        break;
300✔
295
      }
300✔
296
      curOrder = svr.second->d_config.order;
800✔
297
      candidates.emplace_back(curNumber++, svr.second);
800✔
298
      positionsMap.push_back(svr.first);
800✔
299
    }
800✔
300
  }
1,700✔
301

302
  if (candidates.empty()) {
500!
303
    return std::nullopt;
×
304
  }
×
305

306
  auto selected = wrandom(candidates, dnsQuestion);
500✔
307
  if (selected) {
500!
308
    return positionsMap.at(*selected - 1);
500✔
309
  }
500✔
310
  return selected;
×
311
}
500✔
312

313
const ServerPolicy::NumberedServerVector& getDownstreamCandidates(const std::string& poolName)
314
{
2✔
315
  return getPool(poolName).getServers();
2✔
316
}
2✔
317

318
const ServerPool& createPoolIfNotExists(const string& poolName)
319
{
400✔
320
  {
400✔
321
    const auto& pools = dnsdist::configuration::getCurrentRuntimeConfiguration().d_pools;
400✔
322
    const auto poolIt = pools.find(poolName);
400✔
323
    if (poolIt != pools.end()) {
400!
324
      return poolIt->second;
×
325
    }
×
326
  }
400✔
327

328
  if (!poolName.empty()) {
400!
329
    vinfolog("Creating pool %s", poolName);
×
330
  }
×
331

332
  dnsdist::configuration::updateRuntimeConfiguration([&poolName](dnsdist::configuration::RuntimeConfiguration& config) {
400✔
333
    config.d_pools.emplace(poolName, ServerPool());
400✔
334
  });
400✔
335

336
  {
400✔
337
    const auto& pools = dnsdist::configuration::getCurrentRuntimeConfiguration().d_pools;
400✔
338
    const auto poolIt = pools.find(poolName);
400✔
339
    return poolIt->second;
400✔
340
  }
400✔
341
}
400✔
342

343
void setPoolPolicy(const string& poolName, std::shared_ptr<ServerPolicy> policy)
344
{
2✔
345
  if (!poolName.empty()) {
2!
346
    vinfolog("Setting pool %s server selection policy to %s", poolName, policy->getName());
×
347
  } else {
2✔
348
    vinfolog("Setting default pool server selection policy to %s", policy->getName());
2!
349
  }
2✔
350

351
  dnsdist::configuration::updateRuntimeConfiguration([&poolName, &policy](dnsdist::configuration::RuntimeConfiguration& config) {
2✔
352
    auto [poolIt, _] = config.d_pools.emplace(poolName, ServerPool());
2✔
353
    poolIt->second.policy = std::move(policy);
2✔
354
  });
2✔
355
}
2✔
356

357
void addServerToPool(const string& poolName, std::shared_ptr<DownstreamState> server)
358
{
966✔
359
  if (!poolName.empty()) {
966✔
360
    vinfolog("Adding server to pool %s", poolName);
100✔
361
  } else {
872✔
362
    vinfolog("Adding server to default pool");
866✔
363
  }
866✔
364

365
  dnsdist::configuration::updateRuntimeConfiguration([&poolName, &server](dnsdist::configuration::RuntimeConfiguration& config) {
966✔
366
    auto [poolIt, _] = config.d_pools.emplace(poolName, ServerPool());
966✔
367
    poolIt->second.addServer(server);
966✔
368
  });
966✔
369
}
966✔
370

371
void removeServerFromPool(const string& poolName, std::shared_ptr<DownstreamState> server)
372
{
19✔
373
  if (!poolName.empty()) {
19✔
374
    vinfolog("Removing server from pool %s", poolName);
9✔
375
  }
9✔
376
  else {
10✔
377
    vinfolog("Removing server from default pool");
10✔
378
  }
10✔
379

380
  dnsdist::configuration::updateRuntimeConfiguration([&poolName, &server](dnsdist::configuration::RuntimeConfiguration& config) {
19✔
381
    auto [poolIt, _] = config.d_pools.emplace(poolName, ServerPool());
19✔
382
    poolIt->second.removeServer(server);
19✔
383
  });
19✔
384
}
19✔
385

386
const ServerPool& getPool(const std::string& poolName)
387
{
5,925✔
388
  const auto& pools = dnsdist::configuration::getCurrentRuntimeConfiguration().d_pools;
5,925✔
389
  auto poolIt = pools.find(poolName);
5,925✔
390
  if (poolIt == pools.end()) {
5,925✔
391
    throw std::out_of_range(std::string("No pool named ") + poolName);
6✔
392
  }
6✔
393

394
  return poolIt->second;
5,919✔
395
}
5,925✔
396

397
ServerPolicy::ServerPolicy(const std::string& name_, const std::string& code): d_name(name_), d_perThreadPolicyCode(code), d_isLua(true), d_isFFI(true), d_isPerThread(true)
2✔
398
{
2✔
399
  LuaContext tmpContext;
2✔
400
  setupLuaLoadBalancingContext(tmpContext);
2✔
401
  auto ret = tmpContext.executeCode<ServerPolicy::ffipolicyfunc_t>(code);
2✔
402
}
2✔
403

404
struct ServerPolicy::PerThreadState
405
{
406
  LuaContext d_luaContext;
407
  std::unordered_map<std::string, ffipolicyfunc_t> d_policies;
408
};
409

410
thread_local std::unique_ptr<ServerPolicy::PerThreadState> ServerPolicy::t_perThreadState;
411

412
const ServerPolicy::ffipolicyfunc_t& ServerPolicy::getPerThreadPolicy() const
413
{
20✔
414
  auto& state = t_perThreadState;
20✔
415
  if (!state) {
20✔
416
    state = std::make_unique<ServerPolicy::PerThreadState>();
2✔
417
    setupLuaLoadBalancingContext(state->d_luaContext);
2✔
418
  }
2✔
419

420
  const auto& policyIt = state->d_policies.find(d_name);
20✔
421
  if (policyIt != state->d_policies.end()) {
20✔
422
    return policyIt->second;
18✔
423
  }
18✔
424

425
  auto newPolicy = state->d_luaContext.executeCode<ServerPolicy::ffipolicyfunc_t>(d_perThreadPolicyCode);
2✔
426
  state->d_policies[d_name] = std::move(newPolicy);
2✔
427
  return state->d_policies.at(d_name);
2✔
428
}
20✔
429

430
ServerPolicy::SelectedBackend ServerPolicy::getSelectedBackend(const ServerPolicy::NumberedServerVector& servers, DNSQuestion& dnsQuestion) const
431
{
8,352✔
432
  ServerPolicy::SelectedBackend result{servers};
8,352✔
433

434
  if (d_isLua) {
8,352✔
435
    if (!d_isFFI) {
3,042✔
436
      std::optional<SelectedServerPosition> position;
3,020✔
437
      {
3,020✔
438
        auto lock = g_lua.lock();
3,020✔
439
        position = d_policy(servers, &dnsQuestion);
3,020✔
440
      }
3,020✔
441
      if (position && *position > 0 && *position <= servers.size()) {
3,020!
442
        result.setSelected(*position - 1);
3,020✔
443
      }
3,020✔
444
      return result;
3,020✔
445
    }
3,020✔
446

447
    dnsdist_ffi_dnsquestion_t dnsq(&dnsQuestion);
22✔
448
    dnsdist_ffi_servers_list_t serversList(servers);
22✔
449
    ServerPolicy::SelectedServerPosition selected = 0;
22✔
450

451
    if (!d_isPerThread) {
22✔
452
      auto lock = g_lua.lock();
2✔
453
      selected = d_ffipolicy(&serversList, &dnsq);
2✔
454
    }
2✔
455
    else {
20✔
456
      const auto& policy = getPerThreadPolicy();
20✔
457
      selected = policy(&serversList, &dnsq);
20✔
458
    }
20✔
459

460
    if (selected >= servers.size()) {
22✔
461
      /* invalid offset, meaning that there is no server available */
462
      return result;
2✔
463
    }
2✔
464

465
    result.setSelected(selected);
20✔
466
    return result;
20✔
467
  }
22✔
468

469
  auto position = d_policy(servers, &dnsQuestion);
5,310✔
470
  if (position && *position > 0 && *position <= servers.size()) {
5,310!
471
    result.setSelected(*position - 1);
5,285✔
472
  }
5,285✔
473

474
  return result;
5,310✔
475
}
8,352✔
476

477
namespace dnsdist::lbpolicies
478
{
479
const std::vector<std::shared_ptr<ServerPolicy>>& getBuiltInPolicies()
480
{
886✔
481
  static const std::vector<std::shared_ptr<ServerPolicy>> s_policies{
886✔
482
    std::make_shared<ServerPolicy>("firstAvailable", firstAvailable, false),
886✔
483
    std::make_shared<ServerPolicy>("roundrobin", roundrobin, false),
886✔
484
    std::make_shared<ServerPolicy>("wrandom", wrandom, false),
886✔
485
    std::make_shared<ServerPolicy>("whashed", whashed, false),
886✔
486
    std::make_shared<ServerPolicy>("chashed", chashed, false),
886✔
487
    std::make_shared<ServerPolicy>("orderedWrandUntag", orderedWrandUntag, false),
886✔
488
    std::make_shared<ServerPolicy>("leastOutstanding", leastOutstanding, false)};
886✔
489
  return s_policies;
886✔
490
}
886✔
491
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc