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

PowerDNS / pdns / 19151133429

06 Nov 2025 02:49PM UTC coverage: 60.704% (-12.3%) from 73.0%
19151133429

push

github

web-flow
Merge pull request #16446 from jsoref/contributing-ai-policy

docs: Mention AI Policy in contributing pull requests

68493 of 178556 branches covered (38.36%)

Branch coverage included in aggregate %.

152002 of 184673 relevant lines covered (82.31%)

7226535.04 hits per line

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

0.39
/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 */
34✔
83
    std::atomic<time_t> lastStatusUpdate{0};
34✔
84
  };
34✔
85

86
public:
87
  IsUpOracle()
88
  {
17✔
89
    d_checkerThreadStarted.clear();
17✔
90
  }
17✔
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
      int http_code = 200;
×
117
      if (cd.opts.count("httpcode") != 0) {
×
118
        http_code = pdns::checked_stoi<int>(cd.opts.at("httpcode"));
×
119
      }
120

×
121
      MiniCurl minicurl(useragent, false);
×
122

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

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

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

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

214
          if (desc.opts.count("interval") != 0) {
×
215
            checkInterval = std::atoi(desc.opts.at("interval").c_str());
216
            if (checkInterval != 0) {
×
217
              interval = std::gcd(interval, checkInterval);
218
            }
219
          }
220

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

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

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

262
      std::this_thread::sleep_until(checkStart + std::chrono::seconds(interval));
×
263
    }
×
264
  }
×
265

266
  typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
267
  SharedLockGuarded<statuses_t> d_statuses;
268

269
  std::unique_ptr<std::thread> d_checkerThread;
270
  std::atomic_flag d_checkerThreadStarted;
×
271

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

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

303
  void setDown(const CheckDesc& cd)
304
  {
×
305
    setStatus(cd, false);
×
306
  }
×
307

×
308
  void setUp(const CheckDesc& cd)
×
309
  {
310
    setStatus(cd, true);
311
  }
312
};
313

×
314
// The return value of this function can be one of three sets of values:
×
315
// - positive integer: the target is up, the return value is its weight.
×
316
//   (1 if weights are not used)
×
317
// - zero: the target is down.
×
318
// - negative integer: the check for this target has not completed yet.
×
319
//   (this value is only reported if the failOnIncompleteCheck option is
×
320
//    set, otherwise zero will be returned)
×
321
//NOLINTNEXTLINE(readability-identifier-length)
322
int IsUpOracle::isUp(const CheckDesc& cd)
323
{
×
324
  if (!d_checkerThreadStarted.test_and_set()) {
×
325
    d_checkerThread = std::make_unique<std::thread>([this] { return checkThread(); });
×
326
  }
327
  time_t now = time(nullptr);
328
  {
329
    auto statuses = d_statuses.read_lock();
330
    auto iter = statuses->find(cd);
×
331
    if (iter != statuses->end()) {
332
      iter->second->lastAccess = now;
333
      if (iter->second->weight > 0) {
334
        return iter->second->weight;
335
      }
336
      return static_cast<int>(iter->second->status);
337
    }
338
  }
×
339
  // try to parse options so we don't insert any malformed content
×
340
  if (cd.opts.count("source")) {
×
341
    ComboAddress src(cd.opts.at("source"));
×
342
  }
×
343
  {
×
344
    auto statuses = d_statuses.write_lock();
×
345
    // Make sure we don't insert new entry twice now we have the lock
×
346
    if (statuses->find(cd) == statuses->end()) {
347
      (*statuses)[cd] = std::make_unique<CheckState>(now);
×
348
    }
×
349
  }
×
350
  // If explicitly asked to fail on incomplete checks, report this (as
×
351
  // a negative value).
×
352
  static const std::string foic{"failOnIncompleteCheck"};
×
353
  if (cd.opts.count(foic) != 0) {
354
    if (cd.opts.at(foic) == "true") {
×
355
      return -1;
356
    }
×
357
  }
×
358
  return 0;
×
359
}
360

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

×
367
int IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts)
×
368
{
×
369
  CheckDesc cd{remote, url, opts};
370
  return isUp(cd);
371
}
372

×
373
IsUpOracle g_up;
×
374
namespace {
375
template<typename T, typename C>
376
bool doCompare(const T& var, const std::string& res, const C& cmp)
×
377
{
×
378
  if(auto country = boost::get<string>(&var))
×
379
    return cmp(*country, res);
380

×
381
  auto countries=boost::get<vector<pair<int,string> > >(&var);
382
  for(const auto& country : *countries) {
×
383
    if(cmp(country.second, res))
×
384
      return true;
385
  }
×
386
  return false;
387
}
388
}
389

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

405
template <typename T>
×
406
static T pickRandom(const vector<T>& items)
×
407
{
×
408
  if (items.empty()) {
×
409
    throw std::invalid_argument("The items list cannot be empty");
×
410
  }
×
411
  return items[dns_random(items.size())];
×
412
}
×
413

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

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

×
434
  for(auto& i : items) {
435
    sum += i.first;
×
436
    pick.emplace_back(sum, i.second);
×
437
  }
438

439
  if (sum == 0) {
440
    throw std::invalid_argument("The sum of items cannot be zero");
441
  }
×
442

×
443
  int r = dns_random(sum);
×
444
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
445
  return p->second;
446
}
×
447

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

458
  for(auto& i : items) {
×
459
    sum += i.first;
×
460
    pick.push_back({sum, i.second});
×
461
  }
462

463
  if (sum == 0) {
464
    throw std::invalid_argument("The sum of items cannot be zero");
×
465
  }
×
466

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

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

483
  for(auto& i : items) {
×
484
    sum += i.first;
×
485
    pick.push_back({sum, i.second});
×
486
  }
487

488
  if (sum == 0) {
489
    throw std::invalid_argument("The sum of items cannot be zero");
×
490
  }
×
491

492
  size_t r = dnsname.hash() % sum;
493
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
494
  return p->second;
495
}
×
496

497
template <typename T>
×
498
static vector<T> pickRandomSample(int n, const vector<T>& items)
499
{
500
  if (items.empty()) {
×
501
    throw std::invalid_argument("The items list cannot be empty");
×
502
  }
×
503

×
504
  vector<T> pick;
×
505
  pick.reserve(items.size());
506

×
507
  for(auto& item : items) {
508
    pick.push_back(item);
×
509
  }
×
510

511
  int count = std::min(std::max<size_t>(0, n), items.size());
512

513
  if (count == 0) {
×
514
    return vector<T>();
×
515
  }
×
516

×
517
  std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
518

519
  vector<T> result = {pick.begin(), pick.begin() + count};
×
520
  return result;
521
}
×
522

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

×
535
static bool getLatLon(const std::string& ip, string& loc)
×
536
{
537
  int latdeg, latmin, londeg, lonmin;
538
  double latsec, lonsec;
×
539
  char lathem='X', lonhem='X';
540

×
541
  double lat = 0, lon = 0;
×
542
  if(!getLatLon(ip, lat, lon))
×
543
    return false;
544

×
545
  if(lat > 0) {
×
546
    lathem='N';
×
547
  }
×
548
  else {
549
    lat = -lat;
550
    lathem='S';
551
  }
×
552

×
553
  if(lon > 0) {
×
554
    lonhem='E';
×
555
  }
×
556
  else {
×
557
    lon = -lon;
×
558
    lonhem='W';
559
  }
×
560

561
  latdeg = lat;
×
562
  latmin = (lat - latdeg)*60.0;
×
563
  latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
564

×
565
  londeg = lon;
×
566
  lonmin = (lon - londeg)*60.0;
×
567
  lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
×
568

×
569
  // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
×
570

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

×
573
  loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
×
574
  return true;
575
}
×
576

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

×
602
static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, domainid_t zoneid)
×
603
{
604
  static LockGuarded<UeberBackend> s_ub;
605

606
  DNSZoneRecord dr;
607
  vector<DNSZoneRecord> ret;
×
608
  {
609
    auto ub = s_ub.lock();
610
    ub->lookup(QType(qtype), name, zoneid);
611
    while (ub->get(dr)) {
×
612
      ret.push_back(dr);
×
613
    }
614
  }
×
615
  return ret;
616
}
617

×
618
static bool getAuth(const ZoneName& name, uint16_t qtype, SOAData* soaData, Netmask remote)
×
619
{
×
620
  static LockGuarded<UeberBackend> s_ub;
×
621

×
622
  {
×
623
    auto ueback = s_ub.lock();
×
624
    return ueback->getAuth(name, qtype, soaData, remote);
×
625
  }
×
626
}
627

628
static std::string getOptionValue(const boost::optional<opts_t>& options, const std::string &name, const std::string &defaultValue)
×
629
{
×
630
  string selector=defaultValue;
×
631
  if(options) {
632
    if(options->count(name))
×
633
      selector=options->find(name)->second;
×
634
  }
×
635
  return selector;
636
}
×
637

×
638
static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
639
{
640
  vector<ComboAddress> ret;
×
641

642
  if(selector=="all")
643
    return candidates;
644
  else if(selector=="random")
×
645
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
646
  else if(selector=="pickclosest")
×
647
    ret.emplace_back(pickclosest(bestwho, candidates));
648
  else if(selector=="hashed")
×
649
    ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates));
×
650
  else {
651
    g_log<<Logger::Warning<<"Lua record called with unknown selector '"<<selector<<"'"<<endl;
652
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
653
  }
654

655
  return ret;
656
}
×
657

658
static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
×
659
{
660
  vector<string> result;
×
661
  result.reserve(items.size());
662

×
663
  for (const auto& item : items) {
664
    result.emplace_back(item.toString());
665
  }
666

667
  return result;
668
}
669

670
static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
671
{
672
  vector<ComboAddress> result;
673
  result.reserve(items.size());
×
674

×
675
  for(const auto& item : items) {
676
    result.emplace_back(ComboAddress(item.second, port));
677
  }
×
678

×
679
  return result;
×
680
}
681

×
682
/**
×
683
 * Reads and unify single or multiple sets of ips :
684
 * - {'192.0.2.1', '192.0.2.2'}
×
685
 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
×
686
 */
687

×
688
static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
689
{
×
690
  vector<vector<ComboAddress>> candidates;
×
691

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

705
static vector<string> convStringList(const iplist_t& items)
706
{
×
707
  vector<string> result;
708
  result.reserve(items.size());
709

×
710
  for(const auto& item : items) {
×
711
    result.emplace_back(item.second);
×
712
  }
×
713

×
714
  return result;
×
715
}
×
716

×
717
static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
×
718
{
719
  vector<pair<int,string> > result;
720
  result.reserve(items.size());
×
721

×
722
  for(const auto& item : items) {
×
723
    result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
724
  }
×
725

×
726
  return result;
×
727
}
728

×
729
bool g_LuaRecordSharedState;
×
730

731
typedef struct AuthLuaRecordContext
732
{
×
733
  ComboAddress          bestwho;
734
  DNSName               qname;
735
  DNSZoneRecord         zone_record;
736
  DNSName               zone;
×
737
  Netmask               remote;
×
738
} lua_record_ctx_t;
739

740
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
×
741

×
742
/*
743
 *  Holds computed hashes for a given entry
×
744
 */
745
struct EntryHashesHolder
×
746
{
747
  std::atomic<size_t> weight;
748
  std::string entry;
749
  SharedLockGuarded<std::vector<unsigned int>> hashes;
750
  std::atomic<time_t> lastUsed;
751

752
  EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
753
  }
754

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

×
774
using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
×
775

776
static SharedLockGuarded<std::map<
×
777
  zone_hashes_key_t, // zoneid qname entry
×
778
  std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
×
779
  >>
780
s_zone_hashes;
×
781

×
782
static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
×
783

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

799
    for (const auto& [key, entry]: *locked) {
800
      if (entry->lastUsed > someTimeAgo) {
801
        toDelete.push_back(key);
802
      }
×
803
    }
×
804
  }
×
805
  if (!toDelete.empty()) {
×
806
    auto wlocked = s_zone_hashes.write_lock();
×
807
    for (const auto& key : toDelete) {
×
808
      wlocked->erase(key);
×
809
    }
×
810
  }
×
811
}
×
812

813
static std::vector<std::shared_ptr<EntryHashesHolder>> getCHashedEntries(const domainid_t zoneId, const std::string& queryName, const std::vector<std::pair<int, std::string>>& items)
×
814
{
×
815
  std::vector<std::shared_ptr<EntryHashesHolder>> result{};
×
816
  std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
×
817

×
818
  {
×
819
    time_t now = time(nullptr);
×
820
    auto locked = s_zone_hashes.read_lock();
×
821

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

836
    for (auto& [key, entry]: newEntries) {
×
837
      result.push_back(entry);
×
838
      (*wlocked)[key] = std::move(entry);
×
839
    }
×
840
  }
841

×
842
  return result;
×
843
}
×
844

845
static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
846
{
×
847
  const auto& zoneId = s_lua_record_ctx->zone_record.domain_id;
×
848
  const auto queryName = s_lua_record_ctx->qname.toString();
×
849
  unsigned int sel = std::numeric_limits<unsigned int>::max();
850
  unsigned int min = std::numeric_limits<unsigned int>::max();
×
851

×
852
  boost::optional<std::string> ret;
×
853
  boost::optional<std::string> first;
×
854

×
855
  cleanZoneHashes();
856

×
857
  auto entries = getCHashedEntries(zoneId, queryName, items);
×
858

859
  ComboAddress::addressOnlyHash addrOnlyHash;
860
  auto qhash = addrOnlyHash(bestwho);
×
861
  for (const auto& entry : entries) {
×
862
    if (!entry->hashesComputed()) {
×
863
      entry->hash();
×
864
    }
×
865
    {
866
      const auto hashes = entry->hashes.read_lock();
×
867
      if (!hashes->empty()) {
×
868
        if (min > *(hashes->begin())) {
869
          min = *(hashes->begin());
×
870
          first = entry->entry;
871
        }
×
872

873
        auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
×
874
        if (hash_it != hashes->end()) {
×
875
          if (*hash_it < sel) {
×
876
            sel = *hash_it;
×
877
            ret = entry->entry;
×
878
          }
×
879
        }
880
      }
881
    }
×
882
  }
×
883
  if (ret != boost::none) {
×
884
    return *ret;
×
885
  }
×
886
  if (first != boost::none) {
887
    return *first;
×
888
  }
×
889
  return {};
×
890
}
891

×
892
static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options, const std::function<int(const ComboAddress&, const opts_t&)>& upcheckf, uint16_t port = 0)
×
893
{
×
894
  vector<vector<ComboAddress> > candidates;
×
895
  opts_t opts;
×
896
  if (options) {
×
897
    opts = *options;
×
898
  }
×
899

×
900
  candidates = convMultiComboAddressList(ips, port);
×
901

902
  bool incompleteCheck{true};
×
903
  for(const auto& unit : candidates) {
904
    vector<ComboAddress> available;
×
905
    for(const auto& address : unit) {
×
906
      int status = upcheckf(address, opts);
907
      if (status > 0) {
908
        available.push_back(address);
×
909
      }
910
      if (status >= 0) {
×
911
        incompleteCheck = false;
×
912
      }
×
913
    }
914
    if(!available.empty()) {
×
915
      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
916
      return convComboAddressListToString(res);
×
917
    }
×
918
  }
×
919

×
920
  // All units down or have not completed their checks yet.
×
921
  if (incompleteCheck) {
×
922
    throw std::runtime_error("if{url,port}up health check has not completed yet");
923
  }
924

×
925
  // Apply backupSelector on all candidates
926
  vector<ComboAddress> ret{};
×
927
  for(const auto& unit : candidates) {
×
928
    ret.insert(ret.end(), unit.begin(), unit.end());
×
929
  }
×
930

×
931
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
×
932
  return convComboAddressListToString(res);
933
}
934

935
// Lua functions available to the user
×
936

×
937
static string lua_latlon()
×
938
{
939
  double lat{0};
940
  double lon{0};
×
941
  getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
×
942
  return std::to_string(lat)+" "+std::to_string(lon);
×
943
}
×
944

945
static string lua_latlonloc()
×
946
{
×
947
  string loc;
×
948
  getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
949
  return loc;
×
950
}
951

952
static string lua_closestMagic()
×
953
{
×
954
  vector<ComboAddress> candidates;
×
955
  // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
×
956
  for (auto label : s_lua_record_ctx->qname.getRawLabels()) {
×
957
    std::replace(label.begin(), label.end(), '-', '.');
×
958
    try {
×
959
      candidates.emplace_back(label);
960
    } catch (const PDNSException& exc) {
×
961
      // no need to continue as we most likely reached the end of the ip list
×
962
      break ;
×
963
    }
×
964
  }
×
965
  return pickclosest(s_lua_record_ctx->bestwho, candidates).toString();
966
}
967

×
968
static string lua_latlonMagic()
×
969
{
970
  auto labels = s_lua_record_ctx->qname.getRawLabels();
×
971
  if (labels.size() < 4) {
×
972
    return {"unknown"};
×
973
  }
×
974
  double lat{0};
×
975
  double lon{0};
976
  getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
×
977
  return std::to_string(lat)+" "+std::to_string(lon);
×
978
}
×
979

×
980
static string lua_createReverse(const string &format, boost::optional<opts_t> exceptions)
981
{
982
  try {
×
983
    auto labels = s_lua_record_ctx->qname.getRawLabels();
×
984
    if (labels.size() < 4) {
×
985
      return {"unknown"};
×
986
    }
×
987

×
988
    vector<ComboAddress> candidates;
×
989

×
990
    // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
×
991
    // exceptions["1.2.3.4"]="bert.powerdns.com" then provides an exception
×
992
    if (exceptions) {
×
993
      ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
×
994
      const auto& uom = *exceptions;
×
995
      for (const auto& address : uom) {
×
996
        if(ComboAddress(address.first, 0) == req) {
×
997
          return address.second;
×
998
        }
×
999
      }
×
1000
    }
×
1001
    boost::format fmt(format);
1002
    fmt.exceptions(boost::io::all_error_bits ^ (boost::io::too_many_args_bit | boost::io::too_few_args_bit));
×
1003
    fmt % labels[3] % labels[2] % labels[1] % labels[0];
1004

1005
    fmt % (labels[3]+"-"+labels[2]+"-"+labels[1]+"-"+labels[0]);
1006

×
1007
    boost::format fmt2("%02x%02x%02x%02x");
×
1008
    for (int i = 3; i >= 0; --i) {
×
1009
      fmt2 % atoi(labels[i].c_str());
×
1010
    }
×
1011

×
1012
    fmt % (fmt2.str());
×
1013

×
1014
    return fmt.str();
×
1015
  }
×
1016
  catch(std::exception& ex) {
×
1017
    g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
×
1018
  }
1019
  return {"error"};
×
1020
}
1021

×
1022
static string lua_createForward()
×
1023
{
×
1024
  static string allZerosIP{"0.0.0.0"};
×
1025
  try {
×
1026
    DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
1027
    if (!record_name.isWildcard()) {
1028
      return allZerosIP;
×
1029
    }
×
1030
    record_name.chopOff();
1031
    DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
1032

×
1033
    // parts is something like ["1", "2", "3", "4", "static"] or
×
1034
    // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
×
1035
    auto parts = rel.getRawLabels();
×
1036
    // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
1037
    if (parts.size() >= 4) {
×
1038
      ComboAddress address(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
×
1039
      return address.toString();
×
1040
    }
×
1041
    if (!parts.empty()) {
×
1042
      auto& input = parts.at(0);
×
1043

×
1044
      // allow a word without - in front, as long as it does not contain anything that could be a number
×
1045
      size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
×
1046
      if (nonhexprefix > 0) {
1047
        input = input.substr(nonhexprefix);
×
1048
      }
1049

×
1050
      // either hex string, or 12-13-14-15
1051
      vector<string> ip_parts;
×
1052

×
1053
      stringtok(ip_parts, input, "-");
×
1054
      if (ip_parts.size() >= 4) {
×
1055
        // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
×
1056
        string ret;
1057
        for (size_t index=4; index > 0; index--) {
1058
          auto octet = ip_parts.at(ip_parts.size() - index);
1059
          auto octetVal = std::stol(octet); // may throw
1060
          if (octetVal >= 0 && octetVal <= 255) {
×
1061
            ret += octet + ".";
×
1062
          } else {
×
1063
            return allZerosIP;
1064
          }
1065
        }
×
1066
        ret.resize(ret.size() - 1); // remove trailing dot after last octet
1067
        return ret;
×
1068
      }
×
1069
      if (input.length() >= 8) {
1070
        auto last8 = input.substr(input.length()-8);
×
1071
        unsigned int part1{0};
×
1072
        unsigned int part2{0};
×
1073
        unsigned int part3{0};
×
1074
        unsigned int part4{0};
×
1075
        if (sscanf(last8.c_str(), "%02x%02x%02x%02x", &part1, &part2, &part3, &part4) == 4) {
×
1076
          ComboAddress address(std::to_string(part1) + "." + std::to_string(part2) + "." + std::to_string(part3) + "." + std::to_string(part4));
×
1077
          return address.toString();
×
1078
        }
×
1079
      }
1080
    }
×
1081
    return allZerosIP;
×
1082
  } catch (const PDNSException &e) {
1083
    return allZerosIP;
×
1084
  }
×
1085
}
×
1086

×
1087
static string lua_createForward6()
×
1088
{
1089
   static string allZerosIP{"::"};
×
1090
   try {
×
1091
     DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
1092
     if (!record_name.isWildcard()) {
×
1093
       return allZerosIP;
1094
     }
×
1095
     record_name.chopOff();
×
1096
     DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
1097

×
1098
     auto parts = rel.getRawLabels();
×
1099
     if (parts.size() == 8) {
×
1100
       string tot;
1101
       for (int chunk = 0; chunk < 8; ++chunk) {
1102
         if (chunk != 0) {
1103
           tot.append(1, ':');
1104
         }
1105
        tot += parts.at(chunk);
1106
      }
×
1107
      ComboAddress address(tot);
1108
      return address.toString();
1109
    }
1110
    if (parts.size() == 1) {
×
1111
      if (parts[0].find('-') != std::string::npos) {
×
1112
        std::replace(parts[0].begin(), parts[0].end(), '-', ':');
×
1113
        ComboAddress address(parts[0]);
×
1114
        return address.toString();
×
1115
      }
×
1116
      if (parts[0].size() >= 32) {
×
1117
        auto ippart = parts[0].substr(parts[0].size()-32);
1118
        auto fulladdress =
×
1119
          ippart.substr(0, 4) + ":" +
×
1120
          ippart.substr(4, 4) + ":" +
×
1121
          ippart.substr(8, 4) + ":" +
×
1122
          ippart.substr(12, 4) + ":" +
×
1123
          ippart.substr(16, 4) + ":" +
1124
          ippart.substr(20, 4) + ":" +
×
1125
          ippart.substr(24, 4) + ":" +
×
1126
          ippart.substr(28, 4);
×
1127

×
1128
        ComboAddress address(fulladdress);
×
1129
        return address.toString();
1130
      }
×
1131
    }
×
1132
    return allZerosIP;
1133
  } catch (const PDNSException &e) {
1134
    return allZerosIP;
×
1135
  }
1136
}
×
1137

1138
static string lua_createReverse6(const string &format, boost::optional<opts_t> exceptions)
×
1139
{
1140
  vector<ComboAddress> candidates;
×
1141

1142
  try {
×
1143
    auto labels= s_lua_record_ctx->qname.getRawLabels();
×
1144
    if (labels.size()<32) {
×
1145
      return {"unknown"};
×
1146
    }
×
1147

×
1148
    boost::format fmt(format);
×
1149
    fmt.exceptions(boost::io::all_error_bits ^ (boost::io::too_many_args_bit | boost::io::too_few_args_bit));
×
1150

×
1151
    string together;
1152
    vector<string> quads;
1153
    for (int chunk = 0; chunk < 8; ++chunk) {
×
1154
      if (chunk != 0) {
×
1155
        together += ":";
1156
      }
1157
      string lquad;
×
1158
      for (int quartet = 0; quartet < 4; ++quartet) {
×
1159
        lquad.append(1, labels[31 - chunk * 4 - quartet][0]);
×
1160
        together += labels[31 - chunk * 4 - quartet][0];
1161
      }
1162
      quads.push_back(std::move(lquad));
×
1163
    }
×
1164
    ComboAddress ip6(together,0);
1165

×
1166
    if (exceptions) {
1167
      auto& addrs=*exceptions;
×
1168
      for(const auto& addr: addrs) {
×
1169
        // this makes sure we catch all forms of the address
1170
        if (ComboAddress(addr.first, 0) == ip6) {
×
1171
          return addr.second;
×
1172
        }
×
1173
      }
×
1174
    }
1175

×
1176
    string dashed=ip6.toString();
×
1177
    std::replace(dashed.begin(), dashed.end(), ':', '-');
×
1178

×
1179
    // https://github.com/PowerDNS/pdns/issues/7524
1180
    if (boost::ends_with(dashed, "-")) {
×
1181
      // "a--a-" -> "a--a-0"
×
1182
      dashed.push_back('0');
×
1183
    }
1184
    if (boost::starts_with(dashed, "-") || dashed.compare(2, 2, "--") == 0) {
×
1185
      // "-a--a" -> "0-a--a"               "aa--a" -> "0aa--a"
1186
      dashed.insert(0, "0");
1187
    }
1188

1189
    for (int byte = 31; byte >= 0; --byte) {
1190
      fmt % labels[byte];
×
1191
    }
×
1192
    fmt % dashed;
1193

1194
    for(const auto& lquad : quads) {
×
1195
      fmt % lquad;
1196
    }
1197

1198
    return fmt.str();
×
1199
  }
1200
  catch(std::exception& ex) {
×
1201
    g_log<<Logger::Error<<"Lua record exception: "<<ex.what()<<endl;
1202
  }
1203
  catch(PDNSException& ex) {
×
1204
    g_log<<Logger::Error<<"Lua record exception: "<<ex.reason<<endl;
×
1205
  }
×
1206
  return {"unknown"};
1207
}
1208

×
1209
static vector<string> lua_filterForward(const string& address, NetmaskGroup& nmg, boost::optional<string> fallback)
1210
{
1211
  ComboAddress caddr(address);
1212

1213
  if (nmg.match(ComboAddress(address))) {
1214
    return {address};
1215
  }
×
1216
  if (fallback) {
×
1217
    if (fallback->empty()) {
1218
      // if fallback is an empty string, return an empty array
×
1219
      return {};
×
1220
    }
×
1221
    return {*fallback};
×
1222
  }
1223

1224
  if (caddr.isIPv4()) {
×
1225
    return {string("0.0.0.0")};
×
1226
  }
1227
  return {"::"};
×
1228
}
1229

1230
/*
×
1231
 * Simplistic test to see if an IP address listens on a certain port
×
1232
 * Will return a single IP address from the set of available IP addresses. If
1233
 * no IP address is available, will return a random element of the set of
1234
 * addresses supplied for testing.
×
1235
 *
×
1236
 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
×
1237
 */
1238
static vector<string> lua_ifportup(int port, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options)
×
1239
{
×
1240
  port = std::max(port, 0);
×
1241
  port = std::min(port, static_cast<int>(std::numeric_limits<uint16_t>::max()));
1242

×
1243
  auto checker = [](const ComboAddress& addr, const opts_t& opts) -> int {
1244
    return g_up.isUp(addr, opts);
1245
  };
1246
  return genericIfUp(ips, std::move(options), checker, port);
1247
}
1248

1249
static vector<string> lua_ifurlextup(const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options)
×
1250
{
1251
  vector<ComboAddress> candidates;
1252
  opts_t opts;
1253
  if (options) {
×
1254
    opts = *options;
×
1255
  }
×
1256

1257
  ComboAddress ca_unspec;
×
1258
  ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1259

×
1260
  // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1261
  bool incompleteCheck{true};
×
1262
  for (const auto& [count, unitmap] : ipurls) {
1263
    // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1264
    vector<ComboAddress> available;
×
1265

×
1266
    for (const auto& [ipStr, url] : unitmap) {
×
1267
      // unit: ["192.0.2.1"] = "https://example.com"
×
1268
      ComboAddress address(ipStr);
×
1269
      candidates.push_back(address);
×
1270
      int status = g_up.isUp(ca_unspec, url, opts);
1271
      if (status > 0) {
×
1272
        available.push_back(address);
×
1273
      }
1274
      if (status >= 0) {
1275
        incompleteCheck = false;
×
1276
      }
×
1277
    }
1278
    if(!available.empty()) {
×
1279
      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
1280
      return convComboAddressListToString(res);
×
1281
    }
1282
  }
×
1283

×
1284
  // All units down or have not completed their checks yet.
×
1285
  if (incompleteCheck) {
×
1286
    throw std::runtime_error("ifexturlup health check has not completed yet");
×
1287
  }
×
1288

×
1289
  // Apply backupSelector on all candidates
×
1290
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
×
1291
  return convComboAddressListToString(res);
×
1292
}
×
1293

×
1294
static vector<string> lua_ifurlup(const std::string& url, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options)
×
1295
{
×
1296
  auto checker = [&url](const ComboAddress& addr, const opts_t& opts) -> int {
×
1297
    return g_up.isUp(addr, url, opts);
1298
  };
1299
  return genericIfUp(ips, std::move(options), checker);
×
1300
}
×
1301

×
1302
/*
1303
 * Returns a random IP address from the supplied list
1304
 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
×
1305
 */
×
1306
static string lua_pickrandom(const iplist_t& ips)
×
1307
{
1308
  vector<string> items = convStringList(ips);
×
1309
  return pickRandom<string>(items);
×
1310
}
×
1311

×
1312
/*
×
1313
 * Based on the hash of `bestwho`, returns an IP address from the list
×
1314
 * supplied, weighted according to the results of isUp calls.
×
1315
 * @example pickselfweighted('http://example.com/weight', { "192.0.2.20", "203.0.113.4", "203.0.113.2" })
1316
 */
×
1317
static string lua_pickselfweighted(const std::string& url, const iplist_t& ips, boost::optional<opts_t> options)
1318
{
1319
  vector< pair<int, ComboAddress> > items;
1320
  opts_t opts;
1321
  if(options) {
1322
    opts = *options;
1323
  }
1324

1325
  items.reserve(ips.capacity());
1326
  bool available = false;
1327

1328
  vector<ComboAddress> conv = convComboAddressList(ips);
1329
  for (auto& entry : conv) {
1330
    int weight = 0;
1331
    weight = g_up.isUp(entry, url, opts);
1332
    if(weight>0) {
×
1333
      available = true;
×
1334
    }
×
1335
    items.emplace_back(weight, entry);
×
1336
  }
×
1337
  if(available) {
×
1338
    return pickWeightedHashed<ComboAddress>(s_lua_record_ctx->bestwho, items).toString();
×
1339
  }
×
1340

×
1341
  // All units down, apply backupSelector on all candidates
1342
  return pickWeightedRandom<ComboAddress>(items).toString();
×
1343
}
×
1344

×
1345
static vector<string> lua_pickrandomsample(int n, const iplist_t& ips)
×
1346
{
×
1347
  vector<string> items = convStringList(ips);
1348
  return pickRandomSample<string>(n, items);
×
1349
}
×
1350

×
1351
static string lua_pickhashed(const iplist_t& ips)
×
1352
{
×
1353
  vector<string> items = convStringList(ips);
×
1354
  return pickHashed<string>(s_lua_record_ctx->bestwho, items);
1355
}
×
1356

×
1357
/*
×
1358
 * Returns a random IP address from the supplied list, as weighted by the
1359
 * various ``weight`` parameters
1360
 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1361
 */
×
1362
static string lua_pickwrandom(const std::unordered_map<int, wiplist_t>& ips)
×
1363
{
×
1364
  vector< pair<int, string> > items = convIntStringPairList(ips);
1365
  return pickWeightedRandom<string>(items);
1366
}
×
1367

×
1368
/*
×
1369
 * Based on the hash of `bestwho`, returns an IP address from the list
×
1370
 * supplied, as weighted by the various `weight` parameters
1371
 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1372
 */
1373
static string lua_pickwhashed(std::unordered_map<int, wiplist_t> ips)
1374
{
×
1375
  vector< pair<int, string> > items;
1376

1377
  items.reserve(ips.size());
1378
  for (auto& entry : ips) {
×
1379
    items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
×
1380
  }
×
1381

1382
  return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
1383
}
1384

1385
/*
1386
 * Based on the hash of the record name, return an IP address from the list
1387
 * supplied, as weighted by the various `weight` parameters
1388
 * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1389
 */
×
1390
static string lua_picknamehashed(std::unordered_map<int, wiplist_t> ips)
1391
{
×
1392
  vector< pair<int, string> > items;
×
1393

×
1394
  items.reserve(ips.size());
×
1395
  for (auto& address : ips) {
1396
    items.emplace_back(atoi(address.second[1].c_str()), address.second[2]);
×
1397
  }
×
1398

1399
  return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
×
1400
}
1401

1402
/*
1403
 * Based on the hash of `bestwho`, returns an IP address from the list
1404
 * supplied, as weighted by the various `weight` parameters and distributed consistently
1405
 * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
×
1406
 */
×
1407
static string lua_pickchashed(const std::unordered_map<int, wiplist_t>& ips)
1408
{
×
1409
  std::vector<std::pair<int, std::string>> items;
×
1410

1411
  items.reserve(ips.size());
×
1412
  for (const auto& entry : ips) {
×
1413
    items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
×
1414
  }
×
1415

1416
  return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
1417
}
1418

1419
static string lua_pickclosest(const iplist_t& ips)
1420
{
1421
  vector<ComboAddress> conv = convComboAddressList(ips);
1422

×
1423
  return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1424
}
1425

×
1426
static void lua_report(const string& /* event */, const boost::optional<string>& /* line */)
×
1427
{
×
1428
  throw std::runtime_error("Script took too long");
×
1429
}
1430

×
1431
static string lua_geoiplookup(const string &address, const GeoIPInterface::GeoIPQueryAttribute attr)
×
1432
{
1433
  return getGeo(address, attr);
1434
}
×
1435

×
1436
using combovar_t = const boost::variant<string,vector<pair<int,string> > >;
×
1437

×
1438
static bool lua_asnum(const combovar_t& asns)
×
1439
{
1440
  string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
1441
  return doCompare(asns, res, [](const std::string& arg1, const std::string& arg2) -> bool {
×
1442
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
×
1443
    });
×
1444
}
1445

1446
static bool lua_continent(const combovar_t& continent)
×
1447
{
×
1448
  string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1449
  return doCompare(continent, res, [](const std::string& arg1, const std::string& arg2) -> bool {
1450
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
1451
    });
1452
}
1453

×
1454
static string lua_continentCode()
1455
{
×
1456
  string unknown("unknown");
×
1457
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1458
  if ( res == unknown ) {
×
1459
   return {"--"};
×
1460
  }
1461
  return res;
1462
}
×
1463

×
1464
static bool lua_country(const combovar_t& var)
×
1465
{
×
1466
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1467
  return doCompare(var, res, [](const std::string& arg1, const std::string& arg2) -> bool {
1468
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
1469
    });
×
1470

×
1471
}
×
1472

×
1473
static string lua_countryCode()
×
1474
{
×
1475
  string unknown("unknown");
×
1476
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1477
  if (res == unknown) {
1478
   return {"--"};
1479
  }
×
1480
  return res;
×
1481
}
1482

×
1483
static bool lua_region(const combovar_t& var)
×
1484
{
1485
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
1486
  return doCompare(var, res, [](const std::string& arg1, const std::string& arg2) -> bool {
1487
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
1488
    });
1489

1490
}
1491

×
1492
static string lua_regionCode()
1493
{
1494
  string unknown("unknown");
1495
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
1496
  if ( res == unknown ) {
1497
   return {"--"};
1498
  }
1499
  return res;
1500
}
1501

1502
static bool lua_netmask(const iplist_t& ips)
1503
{
1504
  for (const auto& addr : ips) {
1505
    Netmask netmask(addr.second);
1506
    if (netmask.match(s_lua_record_ctx->bestwho)) {
1507
      return true;
1508
    }
1509
  }
1510
  return false;
×
1511
}
1512

1513
/* {
1514
     {
1515
      {'192.168.0.0/16', '10.0.0.0/8'},
1516
      {'192.168.20.20', '192.168.20.21'}
1517
     },
1518
     {
×
1519
      {'0.0.0.0/0'}, {'192.0.2.1'}
1520
     }
×
1521
   }
1522
*/
1523
static string lua_view(const vector<pair<int, vector<pair<int, iplist_t> > > >& pairs)
1524
{
1525
  for(const auto& rule : pairs) {
1526
    const auto& netmasks=rule.second[0].second;
1527
    const auto& destinations=rule.second[1].second;
1528
    for(const auto& nmpair : netmasks) {
1529
      Netmask netmask(nmpair.second);
1530
      if (netmask.match(s_lua_record_ctx->bestwho)) {
1531
        if (destinations.empty()) {
1532
          throw std::invalid_argument("The IP list cannot be empty (for netmask " + netmask.toString() + ")");
1533
        }
1534
        return destinations[dns_random(destinations.size())].second;
1535
      }
1536
    }
1537
  }
1538
  return {};
1539
}
×
1540

1541
static vector<string> lua_all(const vector< pair<int,string> >& ips)
1542
{
×
1543
  vector<string> result;
1544
  result.reserve(ips.size());
×
1545

×
1546
  for (const auto& address : ips) {
1547
      result.emplace_back(address.second);
1548
  }
1549
  if(result.empty()) {
1550
    throw std::invalid_argument("The IP list cannot be empty");
1551
  }
1552
  return result;
1553
}
1554

1555
static vector<string> lua_dblookup(const string& record, uint16_t qtype)
1556
{
1557
  ZoneName rec;
1558
  vector<string> ret;
1559
  try {
1560
    rec = ZoneName(record);
×
1561
  }
1562
  catch (const std::exception& e) {
1563
    g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
×
1564
    return ret;
1565
  }
1566
  try {
1567
    SOAData soaData;
1568

1569
    if (!getAuth(rec, qtype, &soaData, s_lua_record_ctx->remote)) {
1570
      return ret;
1571
    }
1572

1573
    vector<DNSZoneRecord> drs = lookup(rec.operator const DNSName&(), qtype, soaData.domain_id);
1574
    for (const auto& drec : drs) {
1575
      ret.push_back(drec.dr.getContent()->getZoneRepresentation());
1576
    }
1577
  }
1578
  catch (std::exception& e) {
1579
    g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
1580
  }
1581
  return ret;
1582
}
1583

×
1584
static void lua_include(LuaContext& lua, const string& record)
1585
{
1586
  DNSName rec;
1587
  try {
1588
    rec = DNSName(record) + s_lua_record_ctx->zone;
×
1589
  } catch (const std::exception &e){
1590
    g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl;
1591
    return;
1592
  }
1593
  try {
1594
    vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zone_record.domain_id);
1595
    for(const auto& zonerecord : drs) {
1596
      auto luarecord = getRR<LUARecordContent>(zonerecord.dr);
1597
      lua.executeCode(luarecord->getCode());
1598
    }
1599
  }
1600
  catch(std::exception& e) {
1601
    g_log<<Logger::Error<<"Failed to load include record for Lua record "<<rec<<": "<<e.what()<<endl;
1602
  }
1603
}
1604

1605
// Lua variables available to the user
1606

1607
static std::unordered_map<std::string, int> lua_variables{
1608
  {"ASn", GeoIPInterface::GeoIPQueryAttribute::ASn},
1609
  {"City", GeoIPInterface::GeoIPQueryAttribute::City},
×
1610
  {"Continent", GeoIPInterface::GeoIPQueryAttribute::Continent},
1611
  {"Country", GeoIPInterface::GeoIPQueryAttribute::Country},
1612
  {"Country2", GeoIPInterface::GeoIPQueryAttribute::Country2},
1613
  {"Name", GeoIPInterface::GeoIPQueryAttribute::Name},
1614
  {"Region", GeoIPInterface::GeoIPQueryAttribute::Region},
1615
  {"Location", GeoIPInterface::GeoIPQueryAttribute::Location}
1616
};
1617

1618
static void setupLuaRecords(LuaContext& lua)
1619
{
1620
  lua.writeFunction("report", [](const string& event, const boost::optional<string>& line) -> void {
1621
      lua_report(event, line);
1622
    });
1623

1624
  lua.writeFunction("latlon", []() -> string {
1625
      return lua_latlon();
1626
    });
1627
  lua.writeFunction("latlonloc", []() -> string {
1628
      return lua_latlonloc();
1629
    });
1630
  lua.writeFunction("closestMagic", []() -> string {
1631
      return lua_closestMagic();
1632
    });
1633
  lua.writeFunction("latlonMagic", []()-> string {
1634
      return lua_latlonMagic();
1635
    });
1636

1637
  lua.writeFunction("createForward", []() -> string {
1638
      return lua_createForward();
1639
    });
1640
  lua.writeFunction("createForward6", []() -> string {
1641
      return lua_createForward6();
1642
    });
1643

1644
  lua.writeFunction("createReverse", [](const string &format, boost::optional<opts_t> exceptions) -> string {
1645
      return lua_createReverse(format, std::move(exceptions));
1646
    });
1647
  lua.writeFunction("createReverse6", [](const string &format, boost::optional<opts_t> exceptions) -> string {
1648
      return lua_createReverse6(format, std::move(exceptions));
1649
    });
1650

1651
  lua.writeFunction("filterForward", [](const string& address, NetmaskGroup& nmg, boost::optional<string> fallback) -> vector<string> {
1652
      return lua_filterForward(address, nmg, std::move(fallback));
1653
    });
1654

1655
  lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options) -> vector<string> {
1656
      return lua_ifportup(port, ips, std::move(options));
1657
    });
1658

1659
  lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) -> vector<string> {
1660
      return lua_ifurlextup(ipurls, std::move(options));
1661
    });
1662

1663
  lua.writeFunction("ifurlup", [](const std::string& url, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options) -> vector<string> {
1664
      return lua_ifurlup(url, ips, std::move(options));
1665
    });
1666

1667
  lua.writeFunction("pickrandom", [](const iplist_t& ips) -> string {
1668
      return lua_pickrandom(ips);
1669
    });
1670

1671
  lua.writeFunction("pickselfweighted", [](const std::string& url, const iplist_t& ips, boost::optional<opts_t> options) -> string {
1672
      return lua_pickselfweighted(url, ips, std::move(options));
1673
    });
1674

1675
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) -> vector<string> {
1676
      return lua_pickrandomsample(n, ips);
1677
    });
1678

1679
  lua.writeFunction("pickhashed", [](const iplist_t& ips) -> string {
1680
      return lua_pickhashed(ips);
1681
    });
1682
  lua.writeFunction("pickwrandom", [](const std::unordered_map<int, wiplist_t>& ips) -> string {
1683
      return lua_pickwrandom(ips);
1684
    });
1685

1686
  lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t> ips) -> string {
1687
      return lua_pickwhashed(std::move(ips));
1688
    });
1689

1690
  lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t> ips) -> string {
1691
      return lua_picknamehashed(std::move(ips));
1692
    });
1693
  lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) -> string {
1694
      return lua_pickchashed(ips);
1695
    });
1696

1697
  lua.writeFunction("pickclosest", [](const iplist_t& ips) -> string {
1698
      return lua_pickclosest(ips);
1699
    });
1700

1701
  lua.writeFunction("geoiplookup", [](const string &address, const GeoIPInterface::GeoIPQueryAttribute attr) -> string {
1702
      return lua_geoiplookup(address, attr);
1703
    });
1704

1705
  lua.writeFunction("asnum", [](const combovar_t& asns) -> bool {
1706
      return lua_asnum(asns);
1707
    });
1708
  lua.writeFunction("continent", [](const combovar_t& continent) -> bool {
1709
      return lua_continent(continent);
1710
    });
1711
  lua.writeFunction("continentCode", []() -> string {
1712
      return lua_continentCode();
1713
    });
1714
  lua.writeFunction("country", [](const combovar_t& var) -> bool {
1715
      return lua_country(var);
1716
    });
1717
  lua.writeFunction("countryCode", []() -> string {
1718
      return lua_countryCode();
1719
    });
1720
  lua.writeFunction("region", [](const combovar_t& var) -> bool {
1721
      return lua_region(var);
1722
    });
1723
  lua.writeFunction("regionCode", []() -> string {
1724
      return lua_regionCode();
1725
    });
1726
  lua.writeFunction("netmask", [](const iplist_t& ips) -> bool {
1727
      return lua_netmask(ips);
1728
    });
1729
  lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& pairs) -> string {
1730
      return lua_view(pairs);
1731
    });
1732

1733
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) -> vector<string> {
1734
      return lua_all(ips);
1735
    });
1736

1737
  lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) -> vector<string> {
1738
      return lua_dblookup(record, qtype);
1739
    });
1740

1741
  lua.writeFunction("include", [&lua](const string& record) -> void {
1742
      lua_include(lua, record);
1743
    });
1744

1745
  lua.writeVariable("GeoIPQueryAttribute", lua_variables);
1746
}
1747

1748
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)
1749
{
1750
  std::vector<shared_ptr<DNSRecordContent>> ret;
1751

1752
  try {
1753
    if(!LUA ||                  // we don't have a Lua state yet
1754
       !g_LuaRecordSharedState) { // or we want a new one even if we had one
1755
      LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
1756
      setupLuaRecords(*LUA->getLua());
1757
    }
1758

1759
    LuaContext& lua = *LUA->getLua();
1760

1761
    s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
1762
    s_lua_record_ctx->qname = query;
1763
    s_lua_record_ctx->zone_record = zone_record;
1764
    s_lua_record_ctx->zone = zone;
×
1765
    s_lua_record_ctx->remote = dnsp.getRealRemote();
×
1766

1767
    lua.writeVariable("qname", query);
1768
    lua.writeVariable("zone", zone);
1769
    lua.writeVariable("zoneid", zone_record.domain_id);
1770
    lua.writeVariable("who", dnsp.getInnerRemote());
1771
    lua.writeVariable("localwho", dnsp.getLocal());
1772
    lua.writeVariable("dh", static_cast<const dnsheader*>(&dnsp.d));
1773
    lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
1774
    lua.writeVariable("tcp", dnsp.d_tcp);
1775
    lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
1776
    if(dnsp.hasEDNSSubnet()) {
1777
      lua.writeVariable("ecswho", dnsp.getRealRemote());
1778
      s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
1779
    }
1780
    else {
1781
      lua.writeVariable("ecswho", nullptr);
1782
      s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
1783
    }
1784
    lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
1785

1786
    if (g_luaRecordExecLimit > 0) {
1787
      lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
1788
    }
1789

×
1790
    string actual;
1791
    if(!code.empty() && code[0]!=';')
1792
      actual = "return " + code;
1793
    else
1794
      actual = code.substr(1);
1795

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

1798
    vector<string> contents;
1799
    if(auto str = boost::get<string>(&content))
×
1800
      contents.push_back(*str);
1801
    else
1802
      for(const auto& c : boost::get<vector<pair<int,string>>>(content))
1803
        contents.push_back(c.second);
1804

1805
    for(const auto& content_it: contents) {
×
1806
      if(qtype==QType::TXT)
1807
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
1808
      else
1809
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
1810
    }
1811
  } catch(std::exception &e) {
1812
    g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
1813
    try {
×
1814
      std::rethrow_if_nested(e);
1815
      g_log<<endl;
1816
    } catch(const std::exception& ne) {
×
1817
      g_log << ": " << ne.what() << std::endl;
1818
    }
1819
    catch(const PDNSException& ne) {
×
1820
      g_log << ": " << ne.reason << std::endl;
×
1821
    }
1822
    throw ;
1823
  }
1824

1825
  return ret;
1826
}
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