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

PowerDNS / pdns / 13112099868

03 Feb 2025 11:07AM UTC coverage: 64.702% (+0.005%) from 64.697%
13112099868

Pull #15054

github

web-flow
Merge 1f4968bf2 into 7e414adc8
Pull Request #15054: more clang-tidy fixups

38329 of 90324 branches covered (42.44%)

Branch coverage included in aggregate %.

43 of 59 new or added lines in 20 files covered. (72.88%)

510 existing lines in 19 files now uncovered.

127840 of 166499 relevant lines covered (76.78%)

4721746.76 hits per line

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

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

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

24
   investigate IPv6
25

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

28
   ponder ECS scopemask setting
29

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

32
   add attribute for certificate check in genericIfUp
33

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

37
   pool of UeberBackends?
38

39
   Pool checks ?
40
 */
41

42
extern int  g_luaRecordExecLimit;
43

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

649
  return ret;
×
650
}
×
651

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

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

661
  return result;
×
662
}
×
663

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

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

673
  return result;
×
674
}
×
675

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

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

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

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

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

708
  return result;
×
709
}
×
710

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

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

720
  return result;
×
721
}
×
722

723
bool g_LuaRecordSharedState;
724

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

733
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
734

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

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

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

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

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

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

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

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

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

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

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

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

835
  return result;
×
836
}
×
837

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

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

848
  cleanZoneHashes();
×
849

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

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

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

885
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)
886
{
×
887
  vector<vector<ComboAddress> > candidates;
×
888
  opts_t opts;
×
889
  if(options)
×
890
    opts = *options;
×
891

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

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

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

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

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

952

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

959
        vector<ComboAddress> candidates;
×
960

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1108

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

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

1132
        string dashed=ip6.toString();
×
NEW
1133
        std::replace(dashed.begin(), dashed.end(), ':', '-');
×
1134

1135
        // https://github.com/PowerDNS/pdns/issues/7524
1136
        if (boost::ends_with(dashed, "-")) {
×
1137
          // "a--a-" -> "a--a-0"
UNCOV
1138
          dashed.push_back('0');
×
1139
        }
×
1140
        if (boost::starts_with(dashed, "-") || dashed.compare(2, 2, "--") == 0) {
×
1141
          // "-a--a" -> "0-a--a"               "aa--a" -> "0aa--a"
1142
          dashed.insert(0, "0");
×
1143
        }
×
1144

1145
        for(int i=31; i>=0; --i)
×
1146
          fmt % labels[i];
×
1147
        fmt % dashed;
×
1148

1149
        for(const auto& lquad : quads)
×
1150
          fmt % lquad;
×
1151

UNCOV
1152
        return fmt.str();
×
1153
      }
×
1154
      catch(std::exception& ex) {
×
UNCOV
1155
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.what()<<endl;
×
1156
      }
×
1157
      catch(PDNSException& ex) {
×
1158
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl;
×
1159
      }
×
1160
      return std::string("unknown");
×
UNCOV
1161
    });
×
1162

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

UNCOV
1166
      if (nmg.match(ComboAddress(address))) {
×
1167
        return {address};
×
1168
      } else {
×
1169
        if (fallback) {
×
1170
          if (fallback->empty()) {
×
1171
            // if fallback is an empty string, return an empty array
1172
            return {};
×
1173
          }
×
UNCOV
1174
          return {*fallback};
×
UNCOV
1175
        }
×
1176

UNCOV
1177
        if (ca.isIPv4()) {
×
UNCOV
1178
          return {string("0.0.0.0")};
×
UNCOV
1179
        } else {
×
UNCOV
1180
          return {string("::")};
×
UNCOV
1181
        }
×
UNCOV
1182
      }
×
1183
    });
×
1184

1185
  /*
1186
   * Simplistic test to see if an IP address listens on a certain port
1187
   * Will return a single IP address from the set of available IP addresses. If
1188
   * no IP address is available, will return a random element of the set of
1189
   * addresses supplied for testing.
1190
   *
1191
   * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
1192
   */
1193
  lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, const boost::optional<std::unordered_map<string,string>> options) {
×
1194
    port = std::max(port, 0);
×
1195
    port = std::min(port, static_cast<int>(std::numeric_limits<uint16_t>::max()));
×
1196

1197
    auto checker = [](const ComboAddress& addr, const opts_t& opts) {
×
UNCOV
1198
      return g_up.isUp(addr, opts);
×
1199
    };
×
1200
    return genericIfUp(ips, options, checker, port);
×
UNCOV
1201
  });
×
1202

1203
  lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) {
×
UNCOV
1204
      vector<ComboAddress> candidates;
×
1205
      opts_t opts;
×
UNCOV
1206
      if(options)
×
1207
        opts = *options;
×
1208

1209
      ComboAddress ca_unspec;
×
1210
      ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1211

1212
      // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1213
      for (const auto& [count, unitmap] : ipurls) {
×
1214
        // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1215
        vector<ComboAddress> available;
×
1216

1217
        for (const auto& [ipStr, url] : unitmap) {
×
1218
          // unit: ["192.0.2.1"] = "https://example.com"
1219
          ComboAddress ip(ipStr);
×
UNCOV
1220
          candidates.push_back(ip);
×
UNCOV
1221
          if (g_up.isUp(ca_unspec, url, opts)) {
×
1222
            available.push_back(ip);
×
1223
          }
×
1224
        }
×
UNCOV
1225
        if(!available.empty()) {
×
1226
          vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
1227
          return convComboAddressListToString(res);
×
1228
        }
×
UNCOV
1229
      }
×
1230

1231
      // All units down, apply backupSelector on all candidates
1232
      vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
×
1233
      return convComboAddressListToString(res);
×
1234
    });
×
1235

UNCOV
1236
  lua.writeFunction("ifurlup", [](const std::string& url,
×
UNCOV
1237
                                          const boost::variant<iplist_t, ipunitlist_t>& ips,
×
UNCOV
1238
                                          boost::optional<opts_t> options) {
×
1239

1240
    auto checker = [&url](const ComboAddress& addr, const opts_t& opts) {
×
1241
        return g_up.isUp(addr, url, opts);
×
1242
      };
×
UNCOV
1243
      return genericIfUp(ips, options, checker);
×
UNCOV
1244
    });
×
1245
  /*
1246
   * Returns a random IP address from the supplied list
1247
   * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
1248
   */
1249
  lua.writeFunction("pickrandom", [](const iplist_t& ips) {
×
1250
      vector<string> items = convStringList(ips);
×
1251
      return pickRandom<string>(items);
×
1252
    });
×
1253

1254
  /*
1255
   * Based on the hash of `bestwho`, returns an IP address from the list
1256
   * supplied, weighted according to the results of isUp calls.
1257
   * @example pickselfweighted('http://example.com/weight', { "192.0.2.20", "203.0.113.4", "203.0.113.2" })
1258
   */
1259
  lua.writeFunction("pickselfweighted", [](const std::string& url,
×
UNCOV
1260
                                             const iplist_t& ips,
×
1261
                                             boost::optional<opts_t> options) {
×
1262
      vector< pair<int, ComboAddress> > items;
×
1263
      opts_t opts;
×
1264
      if(options) {
×
1265
        opts = *options;
×
1266
      }
×
1267

1268
      items.reserve(ips.capacity());
×
1269
      bool available = false;
×
1270

1271
      vector<ComboAddress> conv = convComboAddressList(ips);
×
1272
      for (auto& entry : conv) {
×
UNCOV
1273
        int weight = 0;
×
UNCOV
1274
        weight = g_up.isUp(entry, url, opts);
×
1275
        if(weight>0) {
×
1276
          available = true;
×
UNCOV
1277
        }
×
1278
        items.emplace_back(weight, entry);
×
1279
      }
×
1280
      if(available) {
×
1281
        return pickWeightedHashed<ComboAddress>(s_lua_record_ctx->bestwho, items).toString();
×
UNCOV
1282
      }
×
1283

1284
      // All units down, apply backupSelector on all candidates
1285
      return pickWeightedRandom<ComboAddress>(items).toString();
×
1286
    });
×
1287

UNCOV
1288
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
×
UNCOV
1289
      vector<string> items = convStringList(ips);
×
UNCOV
1290
          return pickRandomSample<string>(n, items);
×
UNCOV
1291
    });
×
1292

1293
  lua.writeFunction("pickhashed", [](const iplist_t& ips) {
×
1294
      vector<string> items = convStringList(ips);
×
1295
      return pickHashed<string>(s_lua_record_ctx->bestwho, items);
×
UNCOV
1296
    });
×
1297
  /*
1298
   * Returns a random IP address from the supplied list, as weighted by the
1299
   * various ``weight`` parameters
1300
   * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1301
   */
1302
  lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
×
1303
      vector< pair<int, string> > items = convIntStringPairList(ips);
×
UNCOV
1304
      return pickWeightedRandom<string>(items);
×
1305
    });
×
1306

1307
  /*
1308
   * Based on the hash of `bestwho`, returns an IP address from the list
1309
   * supplied, as weighted by the various `weight` parameters
1310
   * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1311
   */
UNCOV
1312
  lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) {
×
UNCOV
1313
      vector< pair<int, string> > items;
×
1314

UNCOV
1315
      items.reserve(ips.size());
×
UNCOV
1316
      for (auto& entry : ips) {
×
UNCOV
1317
        items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
×
1318
      }
×
1319

UNCOV
1320
      return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1321
    });
×
1322

1323
  /*
1324
   * Based on the hash of the record name, return an IP address from the list
1325
   * supplied, as weighted by the various `weight` parameters
1326
   * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1327
   */
1328
  lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t > ips) {
×
UNCOV
1329
      vector< pair<int, string> > items;
×
1330

UNCOV
1331
      items.reserve(ips.size());
×
UNCOV
1332
      for(auto& i : ips)
×
UNCOV
1333
      {
×
1334
        items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
×
1335
      }
×
1336

1337
      return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
×
1338
    });
×
1339
  /*
1340
   * Based on the hash of `bestwho`, returns an IP address from the list
1341
   * supplied, as weighted by the various `weight` parameters and distributed consistently
1342
   * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1343
   */
UNCOV
1344
  lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) {
×
1345
    std::vector<std::pair<int, std::string>> items;
×
1346

UNCOV
1347
    items.reserve(ips.size());
×
1348
    for (const auto& entry : ips) {
×
UNCOV
1349
      items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
×
1350
    }
×
1351

1352
    return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1353
  });
×
1354

UNCOV
1355
  lua.writeFunction("pickclosest", [](const iplist_t& ips) {
×
1356
      vector<ComboAddress> conv = convComboAddressList(ips);
×
1357

1358
      return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1359

1360
    });
×
1361

1362
  if (g_luaRecordExecLimit > 0) {
×
UNCOV
1363
      lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1364
  }
×
1365

1366
  lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){
×
1367
      throw std::runtime_error("Script took too long");
×
1368
    });
×
1369

1370
  lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
×
1371
    return getGeo(ip, attr);
×
1372
  });
×
1373
  lua.writeVariable("GeoIPQueryAttribute", std::unordered_map<std::string, int>{{"ASn", GeoIPInterface::GeoIPQueryAttribute::ASn}, {"City", GeoIPInterface::GeoIPQueryAttribute::City}, {"Continent", GeoIPInterface::GeoIPQueryAttribute::Continent}, {"Country", GeoIPInterface::GeoIPQueryAttribute::Country}, {"Country2", GeoIPInterface::GeoIPQueryAttribute::Country2}, {"Name", GeoIPInterface::GeoIPQueryAttribute::Name}, {"Region", GeoIPInterface::GeoIPQueryAttribute::Region}, {"Location", GeoIPInterface::GeoIPQueryAttribute::Location}});
×
1374

1375
  typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
×
1376

1377
  lua.writeFunction("asnum", [](const combovar_t& asns) {
×
1378
      string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
×
1379
      return doCompare(asns, res, [](const std::string& a, const std::string& b) {
×
1380
          return !strcasecmp(a.c_str(), b.c_str());
×
1381
        });
×
1382
    });
×
1383
  lua.writeFunction("continent", [](const combovar_t& continent) {
×
1384
     string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1385
      return doCompare(continent, res, [](const std::string& a, const std::string& b) {
×
1386
          return !strcasecmp(a.c_str(), b.c_str());
×
1387
        });
×
1388
    });
×
1389
  lua.writeFunction("continentCode", []() {
×
1390
      string unknown("unknown");
×
UNCOV
1391
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1392
      if ( res == unknown ) {
×
1393
       return std::string("--");
×
1394
      }
×
1395
      return res;
×
1396
    });
×
1397
  lua.writeFunction("country", [](const combovar_t& var) {
×
1398
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1399
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1400
          return !strcasecmp(a.c_str(), b.c_str());
×
1401
        });
×
1402

1403
    });
×
1404
  lua.writeFunction("countryCode", []() {
×
1405
      string unknown("unknown");
×
UNCOV
1406
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1407
      if ( res == unknown ) {
×
1408
       return std::string("--");
×
1409
      }
×
1410
      return res;
×
1411
    });
×
1412
  lua.writeFunction("region", [](const combovar_t& var) {
×
1413
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1414
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1415
          return !strcasecmp(a.c_str(), b.c_str());
×
1416
        });
×
1417

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

1462
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
×
UNCOV
1463
      vector<string> result;
×
1464
          result.reserve(ips.size());
×
1465

1466
      for(const auto& ip : ips) {
×
1467
          result.emplace_back(ip.second);
×
1468
      }
×
1469
      if(result.empty()) {
×
1470
        throw std::invalid_argument("The IP list cannot be empty");
×
1471
      }
×
1472
      return result;
×
1473
    });
×
1474

1475
  lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) {
×
UNCOV
1476
    DNSName rec;
×
1477
    vector<string> ret;
×
1478
    try {
×
1479
      rec = DNSName(record);
×
UNCOV
1480
    }
×
1481
    catch (const std::exception& e) {
×
1482
      g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
×
1483
      return ret;
×
1484
    }
×
1485
    try {
×
1486
      SOAData soaData;
×
1487

1488
      if (!getAuth(rec, qtype, &soaData)) {
×
1489
        return ret;
×
1490
      }
×
1491

1492
      vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id);
×
1493
      for (const auto& drec : drs) {
×
1494
        ret.push_back(drec.dr.getContent()->getZoneRepresentation());
×
1495
      }
×
1496
    }
×
1497
    catch (std::exception& e) {
×
1498
      g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
×
1499
    }
×
1500
    return ret;
×
1501
  });
×
1502

1503
  lua.writeFunction("include", [&lua](string record) {
×
1504
      DNSName rec;
×
1505
      try {
×
1506
        rec = DNSName(record) + s_lua_record_ctx->zone;
×
1507
      } catch (const std::exception &e){
×
1508
        g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl;
×
1509
        return;
×
1510
      }
×
1511
      try {
×
UNCOV
1512
        vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zone_record.domain_id);
×
UNCOV
1513
        for(const auto& dr : drs) {
×
1514
          auto lr = getRR<LUARecordContent>(dr.dr);
×
1515
          lua.executeCode(lr->getCode());
×
1516
        }
×
1517
      }
×
1518
      catch(std::exception& e) {
×
1519
        g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<rec<<": "<<e.what()<<endl;
×
UNCOV
1520
      }
×
1521
    });
×
UNCOV
1522
}
×
1523

1524
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)
1525
{
×
1526
  if(!LUA ||                  // we don't have a Lua state yet
×
1527
     !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1528
    LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
×
UNCOV
1529
    setupLuaRecords(*LUA->getLua());
×
1530
  }
×
1531

1532
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1533

1534
  LuaContext& lua = *LUA->getLua();
×
1535

1536
  s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1537
  s_lua_record_ctx->qname = query;
×
1538
  s_lua_record_ctx->zone_record = zone_record;
×
1539
  s_lua_record_ctx->zone = zone;
×
1540

1541
  lua.writeVariable("qname", query);
×
1542
  lua.writeVariable("zone", zone);
×
1543
  lua.writeVariable("zoneid", zone_record.domain_id);
×
1544
  lua.writeVariable("who", dnsp.getInnerRemote());
×
1545
  lua.writeVariable("localwho", dnsp.getLocal());
×
1546
  lua.writeVariable("dh", (dnsheader*)&dnsp.d);
×
1547
  lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
×
UNCOV
1548
  lua.writeVariable("tcp", dnsp.d_tcp);
×
1549
  lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
×
1550
  if(dnsp.hasEDNSSubnet()) {
×
1551
    lua.writeVariable("ecswho", dnsp.getRealRemote());
×
1552
    s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
×
1553
  }
×
1554
  else {
×
UNCOV
1555
    lua.writeVariable("ecswho", nullptr);
×
1556
    s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
×
UNCOV
1557
  }
×
1558
  lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
×
1559

1560
  try {
×
1561
    string actual;
×
1562
    if(!code.empty() && code[0]!=';')
×
1563
      actual = "return " + code;
×
UNCOV
1564
    else
×
1565
      actual = code.substr(1);
×
1566

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

1569
    vector<string> contents;
×
1570
    if(auto str = boost::get<string>(&content))
×
1571
      contents.push_back(*str);
×
1572
    else
×
1573
      for(const auto& c : boost::get<vector<pair<int,string>>>(content))
×
1574
        contents.push_back(c.second);
×
1575

1576
    for(const auto& content_it: contents) {
×
1577
      if(qtype==QType::TXT)
×
1578
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
×
1579
      else
×
1580
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
×
1581
    }
×
1582
  } catch(std::exception &e) {
×
1583
    g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
×
UNCOV
1584
    try {
×
1585
      std::rethrow_if_nested(e);
×
1586
      g_log<<endl;
×
UNCOV
1587
    } catch(const std::exception& ne) {
×
UNCOV
1588
      g_log << ": " << ne.what() << std::endl;
×
UNCOV
1589
    }
×
UNCOV
1590
    catch(const PDNSException& ne) {
×
UNCOV
1591
      g_log << ": " << ne.reason << std::endl;
×
UNCOV
1592
    }
×
UNCOV
1593
    throw ;
×
UNCOV
1594
  }
×
1595

UNCOV
1596
  return ret;
×
UNCOV
1597
}
×
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