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

PowerDNS / pdns / 12859868656

20 Jan 2025 02:34AM UTC coverage: 64.689% (-0.004%) from 64.693%
12859868656

Pull #15054

github

web-flow
Merge cf9411489 into f7822a3cc
Pull Request #15054: more clang-tidy fixups

38304 of 90296 branches covered (42.42%)

Branch coverage included in aggregate %.

26 of 33 new or added lines in 12 files covered. (78.79%)

685 existing lines in 15 files now uncovered.

127762 of 166417 relevant lines covered (76.77%)

4838800.7 hits per line

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

0.19
/pdns/lua-record.cc
1
#include <algorithm>
2
#include <thread>
3
#include <future>
4
#include <boost/format.hpp>
5
#include <boost/uuid/string_generator.hpp>
6
#include <utility>
7
#include <algorithm>
8
#include <random>
9
#include "qtype.hh"
10
#include <tuple>
11
#include "version.hh"
12
#include "ext/luawrapper/include/LuaContext.hpp"
13
#include "lock.hh"
14
#include "lua-auth4.hh"
15
#include "sstuff.hh"
16
#include "minicurl.hh"
17
#include "ueberbackend.hh"
18
#include "dns_random.hh"
19
#include "auth-main.hh"
20
#include "../modules/geoipbackend/geoipinterface.hh" // only for the enum
21

22
/* to do:
23
   block AXFR unless TSIG, or override
24

25
   investigate IPv6
26

27
   check the wildcard 'no cache' stuff, we may get it wrong
28

29
   ponder ECS scopemask setting
30

31
   ponder netmask tree from file for huge number of netmasks
32

33
   add attribute for certificate check in genericIfUp
34

35
   add list of current monitors
36
      expire them too?
37

38
   pool of UeberBackends?
39

40
   Pool checks ?
41
 */
42

43
extern int  g_luaRecordExecLimit;
44

45
using iplist_t = vector<pair<int, string> >;
46
using wiplist_t = std::unordered_map<int, string>;
47
using ipunitlist_t = vector<pair<int, iplist_t> >;
48
using opts_t = std::unordered_map<string,string>;
49

50
class IsUpOracle
51
{
52
private:
53
  struct CheckDesc
54
  {
55
    ComboAddress rem;
56
    string url;
57
    opts_t opts;
58
    bool operator<(const CheckDesc& rhs) const
59
    {
×
60
      std::map<string,string> oopts, rhsoopts;
×
61
      for(const auto& m : opts)
×
62
        oopts[m.first]=m.second;
×
63
      for(const auto& m : rhs.opts)
×
64
        rhsoopts[m.first]=m.second;
×
65

66
      return std::tuple(rem, url, oopts) <
×
67
        std::tuple(rhs.rem, rhs.url, rhsoopts);
×
68
    }
×
69
  };
70
  struct CheckState
71
  {
72
    CheckState(time_t _lastAccess): lastAccess(_lastAccess) {}
×
73
    /* current status */
74
    std::atomic<bool> status{false};
75
    /* current weight */
76
    std::atomic<int> weight{0};
77
    /* first check? */
78
    std::atomic<bool> first{true};
79
    /* number of successive checks returning failure */
80
    std::atomic<unsigned int> failures{0};
81
    /* last time the status was accessed */
82
    std::atomic<time_t> lastAccess{0};
83
    /* last time the status was modified */
84
    std::atomic<time_t> lastStatusUpdate{0};
85
  };
86

87
public:
88
  IsUpOracle()
89
  {
256✔
90
    d_checkerThreadStarted.clear();
256✔
91
  }
256✔
92
  ~IsUpOracle() = default;
×
93
  int isUp(const ComboAddress& remote, const opts_t& opts);
94
  int isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts);
95
  //NOLINTNEXTLINE(readability-identifier-length)
96
  int isUp(const CheckDesc& cd);
97

98
private:
99
  void checkURL(const CheckDesc& cd, const bool status, const bool first) // NOLINT(readability-identifier-length)
100
  {
×
101
    setThreadName("pdns/lua-c-url");
×
102

103
    string remstring;
×
104
    try {
×
105
      int timeout = 2;
×
106
      if (cd.opts.count("timeout")) {
×
107
        timeout = std::atoi(cd.opts.at("timeout").c_str());
×
108
      }
×
109
      string useragent = productName();
×
110
      if (cd.opts.count("useragent")) {
×
111
        useragent = cd.opts.at("useragent");
×
112
      }
×
113
      size_t byteslimit = 0;
×
114
      if (cd.opts.count("byteslimit")) {
×
115
        byteslimit = static_cast<size_t>(std::atoi(cd.opts.at("byteslimit").c_str()));
×
116
      }
×
117
      MiniCurl mc(useragent);
×
118

119
      string content;
×
120
      const ComboAddress* rem = nullptr;
×
121
      if(cd.rem.sin4.sin_family != AF_UNSPEC) {
×
122
        rem = &cd.rem;
×
123
        remstring = rem->toString();
×
124
      } else {
×
125
        remstring = "[externally checked IP]";
×
126
      }
×
127

128
      if (cd.opts.count("source")) {
×
129
        ComboAddress src(cd.opts.at("source"));
×
130
        content=mc.getURL(cd.url, rem, &src, timeout, false, false, byteslimit);
×
131
      }
×
132
      else {
×
133
        content=mc.getURL(cd.url, rem, nullptr, timeout, false, false, byteslimit);
×
134
      }
×
135
      if (cd.opts.count("stringmatch") && content.find(cd.opts.at("stringmatch")) == string::npos) {
×
136
        throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % cd.opts.at("stringmatch")));
×
137
      }
×
138

139
      int weight = 0;
×
140
      try {
×
141
        weight = stoi(content);
×
142
        if(!status) {
×
143
          g_log<<Logger::Info<<"LUA record monitoring declaring "<<remstring<<" UP for URL "<<cd.url<<"!"<<" with WEIGHT "<<content<<"!"<<endl;
×
144
        }
×
145
      }
×
146
      catch (const std::exception&) {
×
147
        if(!status) {
×
148
          g_log<<Logger::Info<<"LUA record monitoring declaring "<<remstring<<" UP for URL "<<cd.url<<"!"<<endl;
×
149
        }
×
150
      }
×
151

152
      setWeight(cd, weight);
×
153
      setUp(cd);
×
154
    }
×
155
    catch(std::exception& ne) {
×
156
      if(status || first)
×
157
        g_log<<Logger::Info<<"LUA record monitoring declaring "<<remstring<<" DOWN for URL "<<cd.url<<", error: "<<ne.what()<<endl;
×
158
      setWeight(cd, 0);
×
159
      setDown(cd);
×
160
    }
×
161
  }
×
162
  void checkTCP(const CheckDesc& cd, const bool status, const bool first) { // NOLINT(readability-identifier-length)
×
163
    setThreadName("pdns/lua-c-tcp");
×
164
    try {
×
165
      int timeout = 2;
×
166
      if (cd.opts.count("timeout")) {
×
167
        timeout = std::atoi(cd.opts.at("timeout").c_str());
×
168
      }
×
169
      Socket s(cd.rem.sin4.sin_family, SOCK_STREAM);
×
170
      ComboAddress src;
×
171
      s.setNonBlocking();
×
172
      if (cd.opts.count("source")) {
×
173
        src = ComboAddress(cd.opts.at("source"));
×
174
        s.bind(src);
×
175
      }
×
176
      s.connect(cd.rem, timeout);
×
177
      if (!status) {
×
178
        g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" ";
×
179
        if(cd.opts.count("source"))
×
180
          g_log<<"(source "<<src.toString()<<") ";
×
181
        g_log<<"UP!"<<endl;
×
182
      }
×
183
      setUp(cd);
×
184
    }
×
185
    catch (const NetworkError& ne) {
×
186
      if(status || first) {
×
187
        g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" DOWN: "<<ne.what()<<endl;
×
188
      }
×
189
      setDown(cd);
×
190
    }
×
191
  }
×
192
  void checkThread()
193
  {
×
194
    setThreadName("pdns/luaupcheck");
×
195
    while (true)
×
196
    {
×
197
      std::chrono::system_clock::time_point checkStart = std::chrono::system_clock::now();
×
198
      std::vector<std::future<void>> results;
×
199
      std::vector<CheckDesc> toDelete;
×
200
      time_t interval{g_luaHealthChecksInterval};
×
201
      {
×
202
        // make sure there's no insertion
203
        auto statuses = d_statuses.read_lock();
×
204
        for (auto& it: *statuses) {
×
205
          auto& desc = it.first;
×
206
          auto& state = it.second;
×
207
          time_t checkInterval{0};
×
208
          auto lastAccess = std::chrono::system_clock::from_time_t(state->lastAccess);
×
209

210
          if (desc.opts.count("interval") != 0) {
×
211
            checkInterval = std::atoi(desc.opts.at("interval").c_str());
×
212
            if (checkInterval != 0) {
×
213
              interval = std::gcd(interval, checkInterval);
×
214
            }
×
215
          }
×
216

217
          if (not state->first) {
×
218
            time_t nextCheckSecond = state->lastStatusUpdate;
×
219
            if (checkInterval != 0) {
×
220
               nextCheckSecond += checkInterval;
×
221
            }
×
222
            else {
×
223
               nextCheckSecond += g_luaHealthChecksInterval;
×
224
            }
×
225
            if (checkStart < std::chrono::system_clock::from_time_t(nextCheckSecond)) {
×
226
              continue; // too early
×
227
            }
×
228
          }
×
229

230
          if (desc.url.empty()) { // TCP
×
231
            results.push_back(std::async(std::launch::async, &IsUpOracle::checkTCP, this, desc, state->status.load(), state->first.load()));
×
232
          } else { // URL
×
233
            results.push_back(std::async(std::launch::async, &IsUpOracle::checkURL, this, desc, state->status.load(), state->first.load()));
×
234
          }
×
235
          // Give it a chance to run at least once.
236
          // If minimumFailures * interval > lua-health-checks-expire-delay, then a down status will never get reported.
237
          // This is unlikely to be a problem in practice due to the default value of the expire delay being one hour.
238
          if (not state->first &&
×
239
              lastAccess < (checkStart - std::chrono::seconds(g_luaHealthChecksExpireDelay))) {
×
240
            toDelete.push_back(desc);
×
241
          }
×
242
        }
×
243
      }
×
244
      // we can release the lock as nothing will be deleted
245
      for (auto& future: results) {
×
246
        future.wait();
×
247
      }
×
248
      if (!toDelete.empty()) {
×
249
        auto statuses = d_statuses.write_lock();
×
250
        for (auto& it: toDelete) {
×
251
          statuses->erase(it);
×
252
        }
×
253
      }
×
254

255
      // set thread name again, in case std::async surprised us by doing work in this thread
256
      setThreadName("pdns/luaupcheck");
×
257

258
      std::this_thread::sleep_until(checkStart + std::chrono::seconds(interval));
×
259
    }
×
260
  }
×
261

262
  typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
263
  SharedLockGuarded<statuses_t> d_statuses;
264

265
  std::unique_ptr<std::thread> d_checkerThread;
266
  std::atomic_flag d_checkerThreadStarted;
267

268
  void setStatus(const CheckDesc& cd, bool status)
269
  {
×
270
    auto statuses = d_statuses.write_lock();
×
271
    auto& state = (*statuses)[cd];
×
272
    state->lastStatusUpdate = time(nullptr);
×
273
    state->first = false;
×
274
    if (status) {
×
275
      state->failures = 0;
×
276
      state->status = true;
×
277
    } else {
×
278
      unsigned int minimumFailures = 1;
×
279
      if (cd.opts.count("minimumFailures") != 0) {
×
280
        unsigned int value = std::atoi(cd.opts.at("minimumFailures").c_str());
×
281
        if (value != 0) {
×
282
          minimumFailures = std::max(minimumFailures, value);
×
283
        }
×
284
      }
×
285
      // Since `status' was set to false at constructor time, we need to
286
      // recompute its value unconditionally to expose "down, but not enough
287
      // times yet" targets as up.
288
      state->status = ++state->failures < minimumFailures;
×
289
    }
×
290
  }
×
291

292
  //NOLINTNEXTLINE(readability-identifier-length)
293
  void setWeight(const CheckDesc& cd, int weight){
×
294
    auto statuses = d_statuses.write_lock();
×
295
    auto& state = (*statuses)[cd];
×
296
    state->weight = weight;
×
297
  }
×
298

299
  void setDown(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
300
  {
×
301
    //NOLINTNEXTLINE(readability-identifier-length)
×
302
    CheckDesc cd{rem, url, opts};
×
303
    setStatus(cd, false);
×
304
  }
×
305

306
  void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
307
  {
×
308
    CheckDesc cd{rem, url, opts};
×
309

×
310
    setStatus(cd, true);
×
311
  }
×
312

313
  void setDown(const CheckDesc& cd)
314
  {
×
315
    setStatus(cd, false);
×
316
  }
×
317

318
  void setUp(const CheckDesc& cd)
319
  {
×
320
    setStatus(cd, true);
×
321
  }
×
322
};
323

324
//NOLINTNEXTLINE(readability-identifier-length)
325
int IsUpOracle::isUp(const CheckDesc& cd)
326
{
×
327
  if (!d_checkerThreadStarted.test_and_set()) {
×
328
    d_checkerThread = std::make_unique<std::thread>([this] { return checkThread(); });
×
329
  }
×
330
  time_t now = time(nullptr);
×
331
  {
×
332
    auto statuses = d_statuses.read_lock();
×
333
    auto iter = statuses->find(cd);
×
334
    if (iter != statuses->end()) {
×
335
      iter->second->lastAccess = now;
×
336
      if (iter->second->weight > 0) {
×
337
        return iter->second->weight;
×
338
      }
×
339
      return static_cast<int>(iter->second->status);
×
340
    }
×
341
  }
×
342
  // try to parse options so we don't insert any malformed content
343
  if (cd.opts.count("source")) {
×
344
    ComboAddress src(cd.opts.at("source"));
×
345
  }
×
346
  {
×
347
    auto statuses = d_statuses.write_lock();
×
348
    // Make sure we don't insert new entry twice now we have the lock
349
    if (statuses->find(cd) == statuses->end()) {
×
350
      (*statuses)[cd] = std::make_unique<CheckState>(now);
×
351
    }
×
352
  }
×
353
  return 0;
×
354
}
×
355

356
int IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts)
357
{
×
358
  CheckDesc cd{remote, "", opts};
×
359
  return isUp(cd);
×
360
}
×
361

362
int IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts)
363
{
×
364
  CheckDesc cd{remote, url, opts};
×
365
  return isUp(cd);
×
366
}
×
367

368
IsUpOracle g_up;
369
namespace {
370
template<typename T, typename C>
371
bool doCompare(const T& var, const std::string& res, const C& cmp)
372
{
×
373
  if(auto country = boost::get<string>(&var))
×
374
    return cmp(*country, res);
×
375

376
  auto countries=boost::get<vector<pair<int,string> > >(&var);
×
377
  for(const auto& country : *countries) {
×
378
    if(cmp(country.second, res))
×
379
      return true;
×
380
  }
×
381
  return false;
×
382
}
×
383
}
384

385
static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
386
{
×
387
  static bool initialized;
×
388
  extern std::function<std::string(const std::string& ip, int)> g_getGeo;
×
389
  if(!g_getGeo) {
×
390
    if(!initialized) {
×
391
      g_log<<Logger::Error<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl;
×
392
      initialized=true;
×
393
    }
×
394
    return "unknown";
×
395
  }
×
396
  else
×
397
    return g_getGeo(ip, (int)qa);
×
398
}
×
399

400
template <typename T>
401
static T pickRandom(const vector<T>& items)
402
{
×
403
  if (items.empty()) {
×
404
    throw std::invalid_argument("The items list cannot be empty");
×
405
  }
×
406
  return items[dns_random(items.size())];
×
407
}
×
408

409
template <typename T>
410
static T pickHashed(const ComboAddress& who, const vector<T>& items)
411
{
×
412
  if (items.empty()) {
×
413
    throw std::invalid_argument("The items list cannot be empty");
×
414
  }
×
415
  ComboAddress::addressOnlyHash aoh;
×
416
  return items[aoh(who) % items.size()];
×
417
}
×
418

419
template <typename T>
420
static T pickWeightedRandom(const vector< pair<int, T> >& items)
421
{
×
422
  if (items.empty()) {
×
423
    throw std::invalid_argument("The items list cannot be empty");
×
424
  }
×
425
  int sum=0;
×
426
  vector< pair<int, T> > pick;
×
427
  pick.reserve(items.size());
×
428

429
  for(auto& i : items) {
×
430
    sum += i.first;
×
431
    pick.emplace_back(sum, i.second);
×
432
  }
×
433

434
  if (sum == 0) {
×
435
    throw std::invalid_argument("The sum of items cannot be zero");
×
436
  }
×
437

438
  int r = dns_random(sum);
×
439
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
440
  return p->second;
×
441
}
×
442

443
template <typename T>
444
static T pickWeightedHashed(const ComboAddress& bestwho, const vector< pair<int, T> >& items)
445
{
×
446
  if (items.empty()) {
×
447
    throw std::invalid_argument("The items list cannot be empty");
×
448
  }
×
449
  int sum=0;
×
450
  vector< pair<int, T> > pick;
×
451
  pick.reserve(items.size());
×
452

453
  for(auto& i : items) {
×
454
    sum += i.first;
×
455
    pick.push_back({sum, i.second});
×
456
  }
×
457

458
  if (sum == 0) {
×
459
    throw std::invalid_argument("The sum of items cannot be zero");
×
460
  }
×
461

462
  ComboAddress::addressOnlyHash aoh;
×
463
  int r = aoh(bestwho) % sum;
×
464
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
465
  return p->second;
×
466
}
×
467

468
template <typename T>
469
static T pickWeightedNameHashed(const DNSName& dnsname, vector< pair<int, T> >& items)
470
{
×
471
  if (items.empty()) {
×
472
    throw std::invalid_argument("The items list cannot be empty");
×
473
  }
×
474
  size_t sum=0;
×
475
  vector< pair<int, T> > pick;
×
476
  pick.reserve(items.size());
×
477

478
  for(auto& i : items) {
×
479
    sum += i.first;
×
480
    pick.push_back({sum, i.second});
×
481
  }
×
482

483
  if (sum == 0) {
×
484
    throw std::invalid_argument("The sum of items cannot be zero");
×
485
  }
×
486

487
  size_t r = dnsname.hash() % sum;
×
488
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
489
  return p->second;
×
490
}
×
491

492
template <typename T>
493
static vector<T> pickRandomSample(int n, const vector<T>& items)
494
{
×
495
  if (items.empty()) {
×
496
    throw std::invalid_argument("The items list cannot be empty");
×
497
  }
×
498

499
  vector<T> pick;
×
500
  pick.reserve(items.size());
×
501

502
  for(auto& item : items) {
×
503
    pick.push_back(item);
×
504
  }
×
505

506
  int count = std::min(std::max<size_t>(0, n), items.size());
×
507

508
  if (count == 0) {
×
509
    return vector<T>();
×
510
  }
×
511

512
  std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
×
513

514
  vector<T> result = {pick.begin(), pick.begin() + count};
×
515
  return result;
×
516
}
×
517

518
static bool getLatLon(const std::string& ip, double& lat, double& lon)
519
{
×
520
  string inp = getGeo(ip, GeoIPInterface::Location);
×
521
  if(inp.empty())
×
522
    return false;
×
523
  lat=atof(inp.c_str());
×
524
  auto pos=inp.find(' ');
×
525
  if(pos != string::npos)
×
526
    lon=atof(inp.c_str() + pos);
×
527
  return true;
×
528
}
×
529

530
static bool getLatLon(const std::string& ip, string& loc)
531
{
×
532
  int latdeg, latmin, londeg, lonmin;
×
533
  double latsec, lonsec;
×
534
  char lathem='X', lonhem='X';
×
535

536
  double lat = 0, lon = 0;
×
537
  if(!getLatLon(ip, lat, lon))
×
538
    return false;
×
539

540
  if(lat > 0) {
×
541
    lathem='N';
×
542
  }
×
543
  else {
×
544
    lat = -lat;
×
545
    lathem='S';
×
546
  }
×
547

548
  if(lon > 0) {
×
549
    lonhem='E';
×
550
  }
×
551
  else {
×
552
    lon = -lon;
×
553
    lonhem='W';
×
554
  }
×
555

556
  latdeg = lat;
×
557
  latmin = (lat - latdeg)*60.0;
×
558
  latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
×
559

560
  londeg = lon;
×
561
  lonmin = (lon - londeg)*60.0;
×
562
  lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
×
563

564
  // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
565

566
  boost::format fmt("%d %d %d %c %d %d %d %c 0.00m 1.00m 10000.00m 10.00m");
×
567

568
  loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
×
569
  return true;
×
570
}
×
571

572
static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips)
573
{
×
574
  if (wips.empty()) {
×
575
    throw std::invalid_argument("The IP list cannot be empty");
×
576
  }
×
577
  map<double, vector<ComboAddress> > ranked;
×
578
  double wlat=0, wlon=0;
×
579
  getLatLon(bestwho.toString(), wlat, wlon);
×
580
  //        cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
581
  vector<string> ret;
×
582
  for(const auto& c : wips) {
×
583
    double lat=0, lon=0;
×
584
    getLatLon(c.toString(), lat, lon);
×
585
    //          cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
586
    double latdiff = wlat-lat;
×
587
    double londiff = wlon-lon;
×
588
    if(londiff > 180)
×
589
      londiff = 360 - londiff;
×
590
    double dist2=latdiff*latdiff + londiff*londiff;
×
591
    //          cout<<"    distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
592
    ranked[dist2].push_back(c);
×
593
  }
×
594
  return ranked.begin()->second[dns_random(ranked.begin()->second.size())];
×
595
}
×
596

597
static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid)
598
{
×
599
  static LockGuarded<UeberBackend> s_ub;
×
600

601
  DNSZoneRecord dr;
×
602
  vector<DNSZoneRecord> ret;
×
603
  {
×
604
    auto ub = s_ub.lock();
×
605
    ub->lookup(QType(qtype), name, zoneid);
×
606
    while (ub->get(dr)) {
×
607
      ret.push_back(dr);
×
608
    }
×
609
  }
×
610
  return ret;
×
611
}
×
612

613
static bool getAuth(const DNSName& name, uint16_t qtype, SOAData* soaData)
614
{
×
615
  static LockGuarded<UeberBackend> s_ub;
×
616

617
  {
×
618
    auto ueback = s_ub.lock();
×
619
    return ueback->getAuth(name, qtype, soaData);
×
620
  }
×
621
}
×
622

623
static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
624
{
×
625
  string selector=defaultValue;
×
626
  if(options) {
×
627
    if(options->count(name))
×
628
      selector=options->find(name)->second;
×
629
  }
×
630
  return selector;
×
631
}
×
632

633
static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
634
{
×
635
  vector<ComboAddress> ret;
×
636

637
  if(selector=="all")
×
638
    return candidates;
×
639
  else if(selector=="random")
×
640
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
641
  else if(selector=="pickclosest")
×
642
    ret.emplace_back(pickclosest(bestwho, candidates));
×
643
  else if(selector=="hashed")
×
644
    ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates));
×
645
  else {
×
646
    g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl;
×
647
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
648
  }
×
649

650
  return ret;
×
651
}
×
652

653
static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
654
{
×
655
  vector<string> result;
×
656
  result.reserve(items.size());
×
657

658
  for (const auto& item : items) {
×
659
    result.emplace_back(item.toString());
×
660
  }
×
661

662
  return result;
×
663
}
×
664

665
static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
666
{
×
667
  vector<ComboAddress> result;
×
668
  result.reserve(items.size());
×
669

670
  for(const auto& item : items) {
×
671
    result.emplace_back(ComboAddress(item.second, port));
×
672
  }
×
673

674
  return result;
×
675
}
×
676

677
/**
678
 * Reads and unify single or multiple sets of ips :
679
 * - {'192.0.2.1', '192.0.2.2'}
680
 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
681
 */
682

683
static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
684
{
×
685
  vector<vector<ComboAddress>> candidates;
×
686

687
  if(auto simple = boost::get<iplist_t>(&items)) {
×
688
    vector<ComboAddress> unit = convComboAddressList(*simple, port);
×
689
    candidates.push_back(unit);
×
690
  } else {
×
691
    auto units = boost::get<ipunitlist_t>(items);
×
692
    for(const auto& u : units) {
×
693
      vector<ComboAddress> unit = convComboAddressList(u.second, port);
×
694
      candidates.push_back(unit);
×
695
    }
×
696
  }
×
697
  return candidates;
×
698
}
×
699

700
static vector<string> convStringList(const iplist_t& items)
701
{
×
702
  vector<string> result;
×
703
  result.reserve(items.size());
×
704

705
  for(const auto& item : items) {
×
706
    result.emplace_back(item.second);
×
707
  }
×
708

709
  return result;
×
710
}
×
711

712
static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
713
{
×
714
  vector<pair<int,string> > result;
×
715
  result.reserve(items.size());
×
716

717
  for(const auto& item : items) {
×
718
    result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
×
719
  }
×
720

721
  return result;
×
722
}
×
723

724
bool g_LuaRecordSharedState;
725

726
typedef struct AuthLuaRecordContext
727
{
728
  ComboAddress          bestwho;
729
  DNSName               qname;
730
  DNSZoneRecord         zone_record;
731
  DNSName               zone;
732
} lua_record_ctx_t;
733

734
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
735

736
/*
737
 *  Holds computed hashes for a given entry
738
 */
739
struct EntryHashesHolder
740
{
741
  std::atomic<size_t> weight;
742
  std::string entry;
743
  SharedLockGuarded<std::vector<unsigned int>> hashes;
744
  std::atomic<time_t> lastUsed;
745

746
  EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
×
747
  }
×
748

749
  bool hashesComputed() {
×
750
    return weight == hashes.read_lock()->size();
×
751
  }
×
752
  void hash() {
×
753
    auto locked = hashes.write_lock();
×
754
    locked->clear();
×
755
    locked->reserve(weight);
×
756
    size_t count = 0;
×
757
    while (count < weight) {
×
758
      auto value = boost::str(boost::format("%s-%d") % entry % count);
×
759
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
760
      auto whash = burtle(reinterpret_cast<const unsigned char*>(value.data()), value.size(), 0);
×
761
      locked->push_back(whash);
×
762
      ++count;
×
763
    }
×
764
    std::sort(locked->begin(), locked->end());
×
765
  }
×
766
};
767

768
using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
769

770
static SharedLockGuarded<std::map<
771
  zone_hashes_key_t, // zoneid qname entry
772
  std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
773
  >>
774
s_zone_hashes;
775

776
static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
777

778
/**
779
 * every ~g_luaConsistentHashesCleanupInterval, do a cleanup to delete entries that haven't been used in the last g_luaConsistentHashesExpireDelay
780
 */
781
static void cleanZoneHashes()
782
{
×
783
  auto now = time(nullptr);
×
784
  if (s_lastConsistentHashesCleanup > (now - g_luaConsistentHashesCleanupInterval)) {
×
785
    return ;
×
786
  }
×
787
  s_lastConsistentHashesCleanup = now;
×
788
  std::vector<zone_hashes_key_t> toDelete{};
×
789
  {
×
790
    auto locked = s_zone_hashes.read_lock();
×
791
    auto someTimeAgo = now - g_luaConsistentHashesExpireDelay;
×
792

793
    for (const auto& [key, entry]: *locked) {
×
794
      if (entry->lastUsed > someTimeAgo) {
×
795
        toDelete.push_back(key);
×
796
      }
×
797
    }
×
798
  }
×
799
  if (!toDelete.empty()) {
×
800
    auto wlocked = s_zone_hashes.write_lock();
×
801
    for (const auto& key : toDelete) {
×
802
      wlocked->erase(key);
×
803
    }
×
804
  }
×
805
}
×
806

807
static std::vector<std::shared_ptr<EntryHashesHolder>> getCHashedEntries(const int zoneId, const std::string& queryName, const std::vector<std::pair<int, std::string>>& items)
808
{
×
809
  std::vector<std::shared_ptr<EntryHashesHolder>> result{};
×
810
  std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
×
811

812
  {
×
813
    time_t now = time(nullptr);
×
814
    auto locked = s_zone_hashes.read_lock();
×
815

816
    for (const auto& [weight, entry]: items) {
×
817
      auto key = std::make_tuple(zoneId, queryName, entry);
×
818
      if (locked->count(key) == 0) {
×
819
        newEntries[key] = std::make_shared<EntryHashesHolder>(weight, entry, now);
×
820
      } else {
×
821
        locked->at(key)->weight = weight;
×
822
        locked->at(key)->lastUsed = now;
×
823
        result.push_back(locked->at(key));
×
824
      }
×
825
    }
×
826
  }
×
827
  if (!newEntries.empty()) {
×
828
    auto wlocked = s_zone_hashes.write_lock();
×
829

830
    for (auto& [key, entry]: newEntries) {
×
831
      result.push_back(entry);
×
832
      (*wlocked)[key] = std::move(entry);
×
833
    }
×
834
  }
×
835

836
  return result;
×
837
}
×
838

839
static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
840
{
×
841
  const auto& zoneId = s_lua_record_ctx->zone_record.domain_id;
×
842
  const auto queryName = s_lua_record_ctx->qname.toString();
×
843
  unsigned int sel = std::numeric_limits<unsigned int>::max();
×
844
  unsigned int min = std::numeric_limits<unsigned int>::max();
×
845

846
  boost::optional<std::string> ret;
×
847
  boost::optional<std::string> first;
×
848

849
  cleanZoneHashes();
×
850

851
  auto entries = getCHashedEntries(zoneId, queryName, items);
×
852

853
  ComboAddress::addressOnlyHash addrOnlyHash;
×
854
  auto qhash = addrOnlyHash(bestwho);
×
855
  for (const auto& entry : entries) {
×
856
    if (!entry->hashesComputed()) {
×
857
      entry->hash();
×
858
    }
×
859
    {
×
860
      const auto hashes = entry->hashes.read_lock();
×
861
      if (!hashes->empty()) {
×
862
        if (min > *(hashes->begin())) {
×
863
          min = *(hashes->begin());
×
864
          first = entry->entry;
×
865
        }
×
866

867
        auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
×
868
        if (hash_it != hashes->end()) {
×
869
          if (*hash_it < sel) {
×
870
            sel = *hash_it;
×
871
            ret = entry->entry;
×
872
          }
×
873
        }
×
874
      }
×
875
    }
×
876
  }
×
877
  if (ret != boost::none) {
×
878
    return *ret;
×
879
  }
×
880
  if (first != boost::none) {
×
881
    return *first;
×
882
  }
×
883
  return {};
×
884
}
×
885

886
static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options, const std::function<bool(const ComboAddress&, const opts_t&)>& upcheckf, uint16_t port = 0)
887
{
×
888
  vector<vector<ComboAddress> > candidates;
×
889
  opts_t opts;
×
890
  if(options)
×
891
    opts = *options;
×
892

893
  candidates = convMultiComboAddressList(ips, port);
×
894

895
  for(const auto& unit : candidates) {
×
896
    vector<ComboAddress> available;
×
897
    for(const auto& c : unit) {
×
898
      if (upcheckf(c, opts)) {
×
899
        available.push_back(c);
×
900
      }
×
901
    }
×
902
    if(!available.empty()) {
×
903
      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
904
      return convComboAddressListToString(res);
×
905
    }
×
906
  }
×
907

908
  // All units down, apply backupSelector on all candidates
909
  vector<ComboAddress> ret{};
×
910
  for(const auto& unit : candidates) {
×
911
    ret.insert(ret.end(), unit.begin(), unit.end());
×
912
  }
×
913

914
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
×
915
  return convComboAddressListToString(res);
×
916
}
×
917

918
static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cognitive-complexity)
919
{
×
920
  lua.writeFunction("latlon", []() {
×
921
      double lat = 0, lon = 0;
×
922
      getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
×
923
      return std::to_string(lat)+" "+std::to_string(lon);
×
924
    });
×
925
  lua.writeFunction("latlonloc", []() {
×
926
      string loc;
×
927
      getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
×
928
      return loc;
×
929
  });
×
930
  lua.writeFunction("closestMagic", []() {
×
931
      vector<ComboAddress> candidates;
×
932
      // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
933
      for(auto l : s_lua_record_ctx->qname.getRawLabels()) {
×
934
        boost::replace_all(l, "-", ".");
×
935
        try {
×
936
          candidates.emplace_back(l);
×
937
        } catch (const PDNSException& e) {
×
938
          // no need to continue as we most likely reached the end of the ip list
939
          break ;
×
940
        }
×
941
      }
×
942
      return pickclosest(s_lua_record_ctx->bestwho, candidates).toString();
×
943
    });
×
944
  lua.writeFunction("latlonMagic", [](){
×
945
      auto labels= s_lua_record_ctx->qname.getRawLabels();
×
946
      if(labels.size()<4)
×
947
        return std::string("unknown");
×
948
      double lat = 0, lon = 0;
×
949
      getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
×
950
      return std::to_string(lat)+" "+std::to_string(lon);
×
951
    });
×
952

953

954
  lua.writeFunction("createReverse", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
955
      try {
×
956
        auto labels = s_lua_record_ctx->qname.getRawLabels();
×
957
        if(labels.size()<4)
×
958
          return std::string("unknown");
×
959

960
        vector<ComboAddress> candidates;
×
961

962
        // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
963
        // e["1.2.3.4"]="bert.powerdns.com" then provides an exception
964
        if(e) {
×
965
          ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
×
966
          const auto& uom = *e;
×
967
          for(const auto& c : uom)
×
968
            if(ComboAddress(c.first, 0) == req)
×
969
              return c.second;
×
970
        }
×
971
        boost::format fmt(format);
×
972
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
973
        fmt % labels[3] % labels[2] % labels[1] % labels[0];
×
974

975
        fmt % (labels[3]+"-"+labels[2]+"-"+labels[1]+"-"+labels[0]);
×
976

977
        boost::format fmt2("%02x%02x%02x%02x");
×
978
        for(int i=3; i>=0; --i)
×
979
          fmt2 % atoi(labels[i].c_str());
×
980

981
        fmt % (fmt2.str());
×
982

983
        return fmt.str();
×
984
      }
×
985
      catch(std::exception& ex) {
×
986
        g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
×
987
      }
×
988
      return std::string("error");
×
989
    });
×
990
  lua.writeFunction("createForward", []() {
×
991
      static string allZerosIP{"0.0.0.0"};
×
992
      DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
993
      if (!record_name.isWildcard()) {
×
994
        return allZerosIP;
×
995
      }
×
996
      record_name.chopOff();
×
997
      DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
998

999
      // parts is something like ["1", "2", "3", "4", "static"] or
1000
      // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
1001
      auto parts = rel.getRawLabels();
×
1002
      // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
1003
      if(parts.size()>=4) {
×
1004
        try {
×
1005
          ComboAddress ca(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
×
1006
          return ca.toString();
×
1007
        } catch (const PDNSException &e) {
×
1008
          return allZerosIP;
×
1009
        }
×
1010
      } else if (!parts.empty()) {
×
1011
        auto& input = parts.at(0);
×
1012

1013
        // allow a word without - in front, as long as it does not contain anything that could be a number
1014
        size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
×
1015
        if (nonhexprefix > 0) {
×
1016
          input = input.substr(nonhexprefix);
×
1017
        }
×
1018

1019
        // either hex string, or 12-13-14-15
1020
        vector<string> ip_parts;
×
1021

1022
        stringtok(ip_parts, input, "-");
×
1023
        unsigned int x1, x2, x3, x4;
×
1024
        if (ip_parts.size() >= 4) {
×
1025
          // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
1026
          string ret;
×
1027
          for (size_t index=4; index > 0; index--) {
×
1028
            auto octet = ip_parts[ip_parts.size() - index];
×
1029
            try {
×
1030
              auto octetVal = std::stol(octet);
×
1031
              if (octetVal >= 0 && octetVal <= 255) {
×
1032
                ret += ip_parts.at(ip_parts.size() - index) + ".";
×
1033
              } else {
×
1034
                return allZerosIP;
×
1035
              }
×
1036
            } catch (const std::exception &e) {
×
1037
              return allZerosIP;
×
1038
            }
×
1039
          }
×
1040
          ret.resize(ret.size() - 1); // remove trailing dot after last octet
×
1041
          return ret;
×
1042
        }
×
1043
        if(input.length() >= 8) {
×
1044
          auto last8 = input.substr(input.length()-8);
×
1045
          if(sscanf(last8.c_str(), "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
×
1046
            return std::to_string(x1) + "." + std::to_string(x2) + "." + std::to_string(x3) + "." + std::to_string(x4);
×
1047
          }
×
1048
        }
×
1049
      }
×
1050
      return allZerosIP;
×
1051
    });
×
1052

1053
  lua.writeFunction("createForward6", []() {
×
1054
      static string allZerosIP{"::"};
×
1055
      DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
1056
      if (!record_name.isWildcard()) {
×
1057
        return allZerosIP;
×
1058
      }
×
1059
      record_name.chopOff();
×
1060
      DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
1061

1062
      auto parts = rel.getRawLabels();
×
1063
      if(parts.size()==8) {
×
1064
        string tot;
×
1065
        for(int i=0; i<8; ++i) {
×
1066
          if(i)
×
1067
            tot.append(1,':');
×
1068
          tot+=parts[i];
×
1069
        }
×
1070
        ComboAddress ca(tot);
×
1071
        return ca.toString();
×
1072
      }
×
1073
      else if(parts.size()==1) {
×
1074
        if (parts[0].find('-') != std::string::npos) {
×
1075
          boost::replace_all(parts[0],"-",":");
×
1076
          ComboAddress ca(parts[0]);
×
1077
          return ca.toString();
×
1078
        } else {
×
1079
          if (parts[0].size() >= 32) {
×
1080
            auto ippart = parts[0].substr(parts[0].size()-32);
×
1081
            auto fulladdress =
×
1082
              ippart.substr(0, 4) + ":" +
×
1083
              ippart.substr(4, 4) + ":" +
×
1084
              ippart.substr(8, 4) + ":" +
×
1085
              ippart.substr(12, 4) + ":" +
×
1086
              ippart.substr(16, 4) + ":" +
×
1087
              ippart.substr(20, 4) + ":" +
×
1088
              ippart.substr(24, 4) + ":" +
×
1089
              ippart.substr(28, 4);
×
1090

1091
            ComboAddress ca(fulladdress);
×
1092
            return ca.toString();
×
1093
          }
×
1094
        }
×
1095
      }
×
1096

1097
      return allZerosIP;
×
1098
    });
×
1099
  lua.writeFunction("createReverse6", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
1100
      vector<ComboAddress> candidates;
×
1101

1102
      try {
×
1103
        auto labels= s_lua_record_ctx->qname.getRawLabels();
×
1104
        if(labels.size()<32)
×
1105
          return std::string("unknown");
×
1106
        boost::format fmt(format);
×
1107
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
1108

1109

1110
        string together;
×
1111
        vector<string> quads;
×
1112
        for(int i=0; i<8; ++i) {
×
1113
          if(i)
×
1114
            together+=":";
×
1115
          string lquad;
×
1116
          for(int j=0; j <4; ++j) {
×
1117
            lquad.append(1, labels[31-i*4-j][0]);
×
1118
            together += labels[31-i*4-j][0];
×
1119
          }
×
1120
          quads.push_back(lquad);
×
1121
        }
×
1122
        ComboAddress ip6(together,0);
×
1123

1124
        if(e) {
×
1125
          auto& addrs=*e;
×
1126
          for(const auto& addr: addrs) {
×
1127
            // this makes sure we catch all forms of the address
1128
            if(ComboAddress(addr.first,0)==ip6)
×
1129
              return addr.second;
×
1130
          }
×
1131
        }
×
1132

1133
        string dashed=ip6.toString();
×
1134
        boost::replace_all(dashed, ":", "-");
×
1135

1136
        for(int i=31; i>=0; --i)
×
1137
          fmt % labels[i];
×
1138
        fmt % dashed;
×
1139

1140
        for(const auto& lquad : quads)
×
1141
          fmt % lquad;
×
1142

1143
        return fmt.str();
×
1144
      }
×
1145
      catch(std::exception& ex) {
×
1146
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.what()<<endl;
×
1147
      }
×
1148
      catch(PDNSException& ex) {
×
1149
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl;
×
1150
      }
×
1151
      return std::string("unknown");
×
1152
    });
×
1153

1154
  lua.writeFunction("filterForward", [](const string& address, NetmaskGroup& nmg, boost::optional<string> fallback) -> vector<string> {
×
1155
      ComboAddress ca(address);
×
1156

1157
      if (nmg.match(ComboAddress(address))) {
×
1158
        return {address};
×
1159
      } else {
×
1160
        if (fallback) {
×
1161
          if (fallback->empty()) {
×
1162
            // if fallback is an empty string, return an empty array
1163
            return {};
×
1164
          }
×
1165
          return {*fallback};
×
1166
        }
×
1167

1168
        if (ca.isIPv4()) {
×
1169
          return {string("0.0.0.0")};
×
1170
        } else {
×
1171
          return {string("::")};
×
1172
        }
×
1173
      }
×
1174
    });
×
1175

1176
  /*
1177
   * Simplistic test to see if an IP address listens on a certain port
1178
   * Will return a single IP address from the set of available IP addresses. If
1179
   * no IP address is available, will return a random element of the set of
1180
   * addresses supplied for testing.
1181
   *
1182
   * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
1183
   */
NEW
1184
  lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<std::unordered_map<string, string>> options) {
×
NEW
1185
    port = std::clamp(port, 0, static_cast<int>(std::numeric_limits<uint16_t>::max()));
×
1186

NEW
1187
    auto checker = [](const ComboAddress& addr, const opts_t& opts) {
×
NEW
1188
      return g_up.isUp(addr, opts);
×
NEW
1189
    };
×
NEW
1190
    return genericIfUp(ips, std::move(options), checker, port);
×
NEW
1191
  });
×
1192

1193
  lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) {
×
1194
      vector<ComboAddress> candidates;
×
1195
      opts_t opts;
×
1196
      if(options)
×
1197
        opts = *options;
×
1198

1199
      ComboAddress ca_unspec;
×
1200
      ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1201

1202
      // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1203
      for (const auto& [count, unitmap] : ipurls) {
×
1204
        // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1205
        vector<ComboAddress> available;
×
1206

1207
        for (const auto& [ipStr, url] : unitmap) {
×
1208
          // unit: ["192.0.2.1"] = "https://example.com"
1209
          ComboAddress ip(ipStr);
×
1210
          candidates.push_back(ip);
×
1211
          if (g_up.isUp(ca_unspec, url, opts)) {
×
1212
            available.push_back(ip);
×
1213
          }
×
1214
        }
×
1215
        if(!available.empty()) {
×
1216
          vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
1217
          return convComboAddressListToString(res);
×
1218
        }
×
1219
      }
×
1220

1221
      // All units down, apply backupSelector on all candidates
1222
      vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
×
1223
      return convComboAddressListToString(res);
×
1224
    });
×
1225

1226
  lua.writeFunction("ifurlup", [](const std::string& url,
×
1227
                                          const boost::variant<iplist_t, ipunitlist_t>& ips,
×
1228
                                          boost::optional<opts_t> options) {
×
1229

1230
    auto checker = [&url](const ComboAddress& addr, const opts_t& opts) {
×
1231
        return g_up.isUp(addr, url, opts);
×
1232
      };
×
1233
      return genericIfUp(ips, options, checker);
×
1234
    });
×
1235
  /*
1236
   * Returns a random IP address from the supplied list
1237
   * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
1238
   */
1239
  lua.writeFunction("pickrandom", [](const iplist_t& ips) {
×
1240
      vector<string> items = convStringList(ips);
×
1241
      return pickRandom<string>(items);
×
1242
    });
×
1243

1244
  /*
1245
   * Based on the hash of `bestwho`, returns an IP address from the list
1246
   * supplied, weighted according to the results of isUp calls.
1247
   * @example pickselfweighted('http://example.com/weight', { "192.0.2.20", "203.0.113.4", "203.0.113.2" })
1248
   */
1249
  lua.writeFunction("pickselfweighted", [](const std::string& url,
×
1250
                                             const iplist_t& ips,
×
1251
                                             boost::optional<opts_t> options) {
×
1252
      vector< pair<int, ComboAddress> > items;
×
1253
      opts_t opts;
×
1254
      if(options) {
×
1255
        opts = *options;
×
1256
      }
×
1257

1258
      items.reserve(ips.capacity());
×
1259
      bool available = false;
×
1260

1261
      vector<ComboAddress> conv = convComboAddressList(ips);
×
1262
      for (auto& entry : conv) {
×
1263
        int weight = 0;
×
1264
        weight = g_up.isUp(entry, url, opts);
×
1265
        if(weight>0) {
×
1266
          available = true;
×
1267
        }
×
1268
        items.emplace_back(weight, entry);
×
1269
      }
×
1270
      if(available) {
×
1271
        return pickWeightedHashed<ComboAddress>(s_lua_record_ctx->bestwho, items).toString();
×
1272
      }
×
1273

1274
      // All units down, apply backupSelector on all candidates
1275
      return pickWeightedRandom<ComboAddress>(items).toString();
×
1276
    });
×
1277

1278
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
×
1279
      vector<string> items = convStringList(ips);
×
1280
          return pickRandomSample<string>(n, items);
×
1281
    });
×
1282

1283
  lua.writeFunction("pickhashed", [](const iplist_t& ips) {
×
1284
      vector<string> items = convStringList(ips);
×
1285
      return pickHashed<string>(s_lua_record_ctx->bestwho, items);
×
1286
    });
×
1287
  /*
1288
   * Returns a random IP address from the supplied list, as weighted by the
1289
   * various ``weight`` parameters
1290
   * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1291
   */
1292
  lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
×
1293
      vector< pair<int, string> > items = convIntStringPairList(ips);
×
1294
      return pickWeightedRandom<string>(items);
×
1295
    });
×
1296

1297
  /*
1298
   * Based on the hash of `bestwho`, returns an IP address from the list
1299
   * supplied, as weighted by the various `weight` parameters
1300
   * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1301
   */
1302
  lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) {
×
1303
      vector< pair<int, string> > items;
×
1304

1305
      items.reserve(ips.size());
×
1306
      for (auto& entry : ips) {
×
1307
        items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
×
1308
      }
×
1309

1310
      return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1311
    });
×
1312

1313
  /*
1314
   * Based on the hash of the record name, return an IP address from the list
1315
   * supplied, as weighted by the various `weight` parameters
1316
   * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1317
   */
1318
  lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t > ips) {
×
1319
      vector< pair<int, string> > items;
×
1320

1321
      items.reserve(ips.size());
×
1322
      for(auto& i : ips)
×
1323
      {
×
1324
        items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
×
1325
      }
×
1326

1327
      return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
×
1328
    });
×
1329
  /*
1330
   * Based on the hash of `bestwho`, returns an IP address from the list
1331
   * supplied, as weighted by the various `weight` parameters and distributed consistently
1332
   * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1333
   */
1334
  lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) {
×
1335
    std::vector<std::pair<int, std::string>> items;
×
1336

1337
    items.reserve(ips.size());
×
1338
    for (const auto& entry : ips) {
×
1339
      items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
×
1340
    }
×
1341

1342
    return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1343
  });
×
1344

1345
  lua.writeFunction("pickclosest", [](const iplist_t& ips) {
×
1346
      vector<ComboAddress> conv = convComboAddressList(ips);
×
1347

1348
      return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1349

1350
    });
×
1351

1352
  if (g_luaRecordExecLimit > 0) {
×
1353
      lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1354
  }
×
1355

1356
  lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){
×
1357
      throw std::runtime_error("Script took too long");
×
1358
    });
×
1359

1360
  lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
×
1361
    return getGeo(ip, attr);
×
1362
  });
×
1363

1364
  typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
×
1365

1366
  lua.writeFunction("asnum", [](const combovar_t& asns) {
×
1367
      string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
×
1368
      return doCompare(asns, res, [](const std::string& a, const std::string& b) {
×
1369
          return !strcasecmp(a.c_str(), b.c_str());
×
1370
        });
×
1371
    });
×
1372
  lua.writeFunction("continent", [](const combovar_t& continent) {
×
1373
     string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1374
      return doCompare(continent, res, [](const std::string& a, const std::string& b) {
×
1375
          return !strcasecmp(a.c_str(), b.c_str());
×
1376
        });
×
1377
    });
×
1378
  lua.writeFunction("continentCode", []() {
×
1379
      string unknown("unknown");
×
1380
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1381
      if ( res == unknown ) {
×
1382
       return std::string("--");
×
1383
      }
×
1384
      return res;
×
1385
    });
×
1386
  lua.writeFunction("country", [](const combovar_t& var) {
×
1387
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1388
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1389
          return !strcasecmp(a.c_str(), b.c_str());
×
1390
        });
×
1391

1392
    });
×
1393
  lua.writeFunction("countryCode", []() {
×
1394
      string unknown("unknown");
×
1395
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1396
      if ( res == unknown ) {
×
1397
       return std::string("--");
×
1398
      }
×
1399
      return res;
×
1400
    });
×
1401
  lua.writeFunction("region", [](const combovar_t& var) {
×
1402
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1403
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1404
          return !strcasecmp(a.c_str(), b.c_str());
×
1405
        });
×
1406

1407
    });
×
1408
  lua.writeFunction("regionCode", []() {
×
1409
      string unknown("unknown");
×
1410
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1411
      if ( res == unknown ) {
×
1412
       return std::string("--");
×
1413
      }
×
1414
      return res;
×
1415
    });
×
1416
  lua.writeFunction("netmask", [](const iplist_t& ips) {
×
1417
      for(const auto& i :ips) {
×
1418
        Netmask nm(i.second);
×
1419
        if(nm.match(s_lua_record_ctx->bestwho))
×
1420
          return true;
×
1421
      }
×
1422
      return false;
×
1423
    });
×
1424
  /* {
1425
       {
1426
        {'192.168.0.0/16', '10.0.0.0/8'},
1427
        {'192.168.20.20', '192.168.20.21'}
1428
       },
1429
       {
1430
        {'0.0.0.0/0'}, {'192.0.2.1'}
1431
       }
1432
     }
1433
  */
1434
  lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) {
×
1435
      for(const auto& rule : in) {
×
1436
        const auto& netmasks=rule.second[0].second;
×
1437
        const auto& destinations=rule.second[1].second;
×
1438
        for(const auto& nmpair : netmasks) {
×
1439
          Netmask nm(nmpair.second);
×
1440
          if(nm.match(s_lua_record_ctx->bestwho)) {
×
1441
            if (destinations.empty()) {
×
1442
              throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm.toString() + ")");
×
1443
            }
×
1444
            return destinations[dns_random(destinations.size())].second;
×
1445
          }
×
1446
        }
×
1447
      }
×
1448
      return std::string();
×
1449
    });
×
1450

1451
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
×
1452
      vector<string> result;
×
1453
          result.reserve(ips.size());
×
1454

1455
      for(const auto& ip : ips) {
×
1456
          result.emplace_back(ip.second);
×
1457
      }
×
1458
      if(result.empty()) {
×
1459
        throw std::invalid_argument("The IP list cannot be empty");
×
1460
      }
×
1461
      return result;
×
1462
    });
×
1463

1464
  lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) {
×
1465
    DNSName rec;
×
1466
    vector<string> ret;
×
1467
    try {
×
1468
      rec = DNSName(record);
×
1469
    }
×
1470
    catch (const std::exception& e) {
×
1471
      g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
×
1472
      return ret;
×
1473
    }
×
1474
    try {
×
1475
      SOAData soaData;
×
1476

1477
      if (!getAuth(rec, qtype, &soaData)) {
×
1478
        return ret;
×
1479
      }
×
1480

1481
      vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id);
×
1482
      for (const auto& drec : drs) {
×
1483
        ret.push_back(drec.dr.getContent()->getZoneRepresentation());
×
1484
      }
×
1485
    }
×
1486
    catch (std::exception& e) {
×
1487
      g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
×
1488
    }
×
1489
    return ret;
×
1490
  });
×
1491

1492
  lua.writeFunction("include", [&lua](string record) {
×
1493
      DNSName rec;
×
1494
      try {
×
1495
        rec = DNSName(record) + s_lua_record_ctx->zone;
×
1496
      } catch (const std::exception &e){
×
1497
        g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl;
×
1498
        return;
×
1499
      }
×
1500
      try {
×
1501
        vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zone_record.domain_id);
×
1502
        for(const auto& dr : drs) {
×
1503
          auto lr = getRR<LUARecordContent>(dr.dr);
×
1504
          lua.executeCode(lr->getCode());
×
1505
        }
×
1506
      }
×
1507
      catch(std::exception& e) {
×
1508
        g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<rec<<": "<<e.what()<<endl;
×
1509
      }
×
1510
    });
×
1511
}
×
1512

1513
std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSZoneRecord& zone_record, const DNSName& zone, const DNSPacket& dnsp, uint16_t qtype, unique_ptr<AuthLua4>& LUA)
1514
{
×
1515
  if(!LUA ||                  // we don't have a Lua state yet
×
1516
     !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1517
    LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
×
1518
    setupLuaRecords(*LUA->getLua());
×
1519
  }
×
1520

1521
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1522

1523
  LuaContext& lua = *LUA->getLua();
×
1524

1525
  s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1526
  s_lua_record_ctx->qname = query;
×
1527
  s_lua_record_ctx->zone_record = zone_record;
×
1528
  s_lua_record_ctx->zone = zone;
×
1529

1530
  lua.writeVariable("qname", query);
×
1531
  lua.writeVariable("zone", zone);
×
1532
  lua.writeVariable("zoneid", zone_record.domain_id);
×
1533
  lua.writeVariable("who", dnsp.getInnerRemote());
×
1534
  lua.writeVariable("localwho", dnsp.getLocal());
×
1535
  lua.writeVariable("dh", (dnsheader*)&dnsp.d);
×
1536
  lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
×
1537
  lua.writeVariable("tcp", dnsp.d_tcp);
×
1538
  lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
×
1539
  if(dnsp.hasEDNSSubnet()) {
×
1540
    lua.writeVariable("ecswho", dnsp.getRealRemote());
×
1541
    s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
×
1542
  }
×
1543
  else {
×
1544
    lua.writeVariable("ecswho", nullptr);
×
1545
    s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
×
1546
  }
×
1547
  lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
×
1548

1549
  try {
×
1550
    string actual;
×
1551
    if(!code.empty() && code[0]!=';')
×
1552
      actual = "return " + code;
×
1553
    else
×
1554
      actual = code.substr(1);
×
1555

1556
    auto content=lua.executeCode<boost::variant<string, vector<pair<int, string> > > >(actual);
×
1557

1558
    vector<string> contents;
×
1559
    if(auto str = boost::get<string>(&content))
×
1560
      contents.push_back(*str);
×
1561
    else
×
1562
      for(const auto& c : boost::get<vector<pair<int,string>>>(content))
×
1563
        contents.push_back(c.second);
×
1564

1565
    for(const auto& content_it: contents) {
×
1566
      if(qtype==QType::TXT)
×
1567
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
×
1568
      else
×
1569
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
×
1570
    }
×
1571
  } catch(std::exception &e) {
×
1572
    g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
×
1573
    try {
×
1574
      std::rethrow_if_nested(e);
×
1575
      g_log<<endl;
×
1576
    } catch(const std::exception& ne) {
×
1577
      g_log << ": " << ne.what() << std::endl;
×
1578
    }
×
1579
    catch(const PDNSException& ne) {
×
1580
      g_log << ": " << ne.reason << std::endl;
×
1581
    }
×
1582
    throw ;
×
1583
  }
×
1584

1585
  return ret;
×
1586
}
×
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

© 2026 Coveralls, Inc