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

PowerDNS / pdns / 14497954401

16 Apr 2025 04:40PM UTC coverage: 63.538% (+0.05%) from 63.485%
14497954401

Pull #15441

github

web-flow
Merge 8bfeeeb15 into f7c7e299c
Pull Request #15441: [evil] ZoneName, step 2

41812 of 100540 branches covered (41.59%)

Branch coverage included in aggregate %.

357 of 407 new or added lines in 38 files covered. (87.71%)

38 existing lines in 10 files now uncovered.

129019 of 168323 relevant lines covered (76.65%)

3923687.88 hits per line

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

0.18
/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
      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 ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
304
  {
×
305
    //NOLINTNEXTLINE(readability-identifier-length)
×
306
    CheckDesc cd{rem, url, opts};
×
307
    setStatus(cd, false);
×
308
  }
×
309

310
  void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
311
  {
×
312
    CheckDesc cd{rem, url, opts};
×
313

×
314
    setStatus(cd, true);
×
315
  }
×
316

317
  void setDown(const CheckDesc& cd)
318
  {
×
319
    setStatus(cd, false);
×
320
  }
×
321

322
  void setUp(const CheckDesc& cd)
323
  {
×
324
    setStatus(cd, true);
×
325
  }
×
326
};
327

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

375
int IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts)
376
{
×
377
  CheckDesc cd{remote, "", opts};
×
378
  return isUp(cd);
×
379
}
×
380

381
int IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts)
382
{
×
383
  CheckDesc cd{remote, url, opts};
×
384
  return isUp(cd);
×
385
}
×
386

387
IsUpOracle g_up;
388
namespace {
389
template<typename T, typename C>
390
bool doCompare(const T& var, const std::string& res, const C& cmp)
391
{
×
392
  if(auto country = boost::get<string>(&var))
×
393
    return cmp(*country, res);
×
394

395
  auto countries=boost::get<vector<pair<int,string> > >(&var);
×
396
  for(const auto& country : *countries) {
×
397
    if(cmp(country.second, res))
×
398
      return true;
×
399
  }
×
400
  return false;
×
401
}
×
402
}
403

404
static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
405
{
×
406
  static bool initialized;
×
407
  extern std::function<std::string(const std::string& ip, int)> g_getGeo;
×
408
  if(!g_getGeo) {
×
409
    if(!initialized) {
×
410
      g_log<<Logger::Error<<"Lua record attempted to use GeoIPBackend functionality, but backend not launched"<<endl;
×
411
      initialized=true;
×
412
    }
×
413
    return "unknown";
×
414
  }
×
415
  else
×
416
    return g_getGeo(ip, (int)qa);
×
417
}
×
418

419
template <typename T>
420
static T pickRandom(const vector<T>& items)
421
{
×
422
  if (items.empty()) {
×
423
    throw std::invalid_argument("The items list cannot be empty");
×
424
  }
×
425
  return items[dns_random(items.size())];
×
426
}
×
427

428
template <typename T>
429
static T pickHashed(const ComboAddress& who, const vector<T>& items)
430
{
×
431
  if (items.empty()) {
×
432
    throw std::invalid_argument("The items list cannot be empty");
×
433
  }
×
434
  ComboAddress::addressOnlyHash aoh;
×
435
  return items[aoh(who) % items.size()];
×
436
}
×
437

438
template <typename T>
439
static T pickWeightedRandom(const vector< pair<int, T> >& items)
440
{
×
441
  if (items.empty()) {
×
442
    throw std::invalid_argument("The items list cannot be empty");
×
443
  }
×
444
  int sum=0;
×
445
  vector< pair<int, T> > pick;
×
446
  pick.reserve(items.size());
×
447

448
  for(auto& i : items) {
×
449
    sum += i.first;
×
450
    pick.emplace_back(sum, i.second);
×
451
  }
×
452

453
  if (sum == 0) {
×
454
    throw std::invalid_argument("The sum of items cannot be zero");
×
455
  }
×
456

457
  int r = dns_random(sum);
×
458
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
459
  return p->second;
×
460
}
×
461

462
template <typename T>
463
static T pickWeightedHashed(const ComboAddress& bestwho, const vector< pair<int, T> >& items)
464
{
×
465
  if (items.empty()) {
×
466
    throw std::invalid_argument("The items list cannot be empty");
×
467
  }
×
468
  int sum=0;
×
469
  vector< pair<int, T> > pick;
×
470
  pick.reserve(items.size());
×
471

472
  for(auto& i : items) {
×
473
    sum += i.first;
×
474
    pick.push_back({sum, i.second});
×
475
  }
×
476

477
  if (sum == 0) {
×
478
    throw std::invalid_argument("The sum of items cannot be zero");
×
479
  }
×
480

481
  ComboAddress::addressOnlyHash aoh;
×
482
  int r = aoh(bestwho) % sum;
×
483
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
484
  return p->second;
×
485
}
×
486

487
template <typename T>
488
static T pickWeightedNameHashed(const DNSName& dnsname, vector< pair<int, T> >& items)
489
{
×
490
  if (items.empty()) {
×
491
    throw std::invalid_argument("The items list cannot be empty");
×
492
  }
×
493
  size_t sum=0;
×
494
  vector< pair<int, T> > pick;
×
495
  pick.reserve(items.size());
×
496

497
  for(auto& i : items) {
×
498
    sum += i.first;
×
499
    pick.push_back({sum, i.second});
×
500
  }
×
501

502
  if (sum == 0) {
×
503
    throw std::invalid_argument("The sum of items cannot be zero");
×
504
  }
×
505

506
  size_t r = dnsname.hash() % sum;
×
507
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
508
  return p->second;
×
509
}
×
510

511
template <typename T>
512
static vector<T> pickRandomSample(int n, const vector<T>& items)
513
{
×
514
  if (items.empty()) {
×
515
    throw std::invalid_argument("The items list cannot be empty");
×
516
  }
×
517

518
  vector<T> pick;
×
519
  pick.reserve(items.size());
×
520

521
  for(auto& item : items) {
×
522
    pick.push_back(item);
×
523
  }
×
524

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

527
  if (count == 0) {
×
528
    return vector<T>();
×
529
  }
×
530

531
  std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
×
532

533
  vector<T> result = {pick.begin(), pick.begin() + count};
×
534
  return result;
×
535
}
×
536

537
static bool getLatLon(const std::string& ip, double& lat, double& lon)
538
{
×
539
  string inp = getGeo(ip, GeoIPInterface::Location);
×
540
  if(inp.empty())
×
541
    return false;
×
542
  lat=atof(inp.c_str());
×
543
  auto pos=inp.find(' ');
×
544
  if(pos != string::npos)
×
545
    lon=atof(inp.c_str() + pos);
×
546
  return true;
×
547
}
×
548

549
static bool getLatLon(const std::string& ip, string& loc)
550
{
×
551
  int latdeg, latmin, londeg, lonmin;
×
552
  double latsec, lonsec;
×
553
  char lathem='X', lonhem='X';
×
554

555
  double lat = 0, lon = 0;
×
556
  if(!getLatLon(ip, lat, lon))
×
557
    return false;
×
558

559
  if(lat > 0) {
×
560
    lathem='N';
×
561
  }
×
562
  else {
×
563
    lat = -lat;
×
564
    lathem='S';
×
565
  }
×
566

567
  if(lon > 0) {
×
568
    lonhem='E';
×
569
  }
×
570
  else {
×
571
    lon = -lon;
×
572
    lonhem='W';
×
573
  }
×
574

575
  latdeg = lat;
×
576
  latmin = (lat - latdeg)*60.0;
×
577
  latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
×
578

579
  londeg = lon;
×
580
  lonmin = (lon - londeg)*60.0;
×
581
  lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
×
582

583
  // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
584

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

587
  loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
×
588
  return true;
×
589
}
×
590

591
static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips)
592
{
×
593
  if (wips.empty()) {
×
594
    throw std::invalid_argument("The IP list cannot be empty");
×
595
  }
×
596
  map<double, vector<ComboAddress> > ranked;
×
597
  double wlat=0, wlon=0;
×
598
  getLatLon(bestwho.toString(), wlat, wlon);
×
599
  //        cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
600
  vector<string> ret;
×
601
  for(const auto& c : wips) {
×
602
    double lat=0, lon=0;
×
603
    getLatLon(c.toString(), lat, lon);
×
604
    //          cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
605
    double latdiff = wlat-lat;
×
606
    double londiff = wlon-lon;
×
607
    if(londiff > 180)
×
608
      londiff = 360 - londiff;
×
609
    double dist2=latdiff*latdiff + londiff*londiff;
×
610
    //          cout<<"    distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
611
    ranked[dist2].push_back(c);
×
612
  }
×
613
  return ranked.begin()->second[dns_random(ranked.begin()->second.size())];
×
614
}
×
615

616
static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid)
617
{
×
618
  static LockGuarded<UeberBackend> s_ub;
×
619

620
  DNSZoneRecord dr;
×
621
  vector<DNSZoneRecord> ret;
×
622
  {
×
623
    auto ub = s_ub.lock();
×
624
    ub->lookup(QType(qtype), name, zoneid);
×
625
    while (ub->get(dr)) {
×
626
      ret.push_back(dr);
×
627
    }
×
628
  }
×
629
  return ret;
×
630
}
×
631

632
static bool getAuth(const ZoneName& name, uint16_t qtype, SOAData* soaData)
633
{
×
634
  static LockGuarded<UeberBackend> s_ub;
×
635

636
  {
×
637
    auto ueback = s_ub.lock();
×
638
    return ueback->getAuth(name, qtype, soaData);
×
639
  }
×
640
}
×
641

642
static std::string getOptionValue(const boost::optional<opts_t>& options, const std::string &name, const std::string &defaultValue)
643
{
×
644
  string selector=defaultValue;
×
645
  if(options) {
×
646
    if(options->count(name))
×
647
      selector=options->find(name)->second;
×
648
  }
×
649
  return selector;
×
650
}
×
651

652
static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
653
{
×
654
  vector<ComboAddress> ret;
×
655

656
  if(selector=="all")
×
657
    return candidates;
×
658
  else if(selector=="random")
×
659
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
660
  else if(selector=="pickclosest")
×
661
    ret.emplace_back(pickclosest(bestwho, candidates));
×
662
  else if(selector=="hashed")
×
663
    ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates));
×
664
  else {
×
665
    g_log<<Logger::Warning<<"Lua record called with unknown selector '"<<selector<<"'"<<endl;
×
666
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
667
  }
×
668

669
  return ret;
×
670
}
×
671

672
static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
673
{
×
674
  vector<string> result;
×
675
  result.reserve(items.size());
×
676

677
  for (const auto& item : items) {
×
678
    result.emplace_back(item.toString());
×
679
  }
×
680

681
  return result;
×
682
}
×
683

684
static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
685
{
×
686
  vector<ComboAddress> result;
×
687
  result.reserve(items.size());
×
688

689
  for(const auto& item : items) {
×
690
    result.emplace_back(ComboAddress(item.second, port));
×
691
  }
×
692

693
  return result;
×
694
}
×
695

696
/**
697
 * Reads and unify single or multiple sets of ips :
698
 * - {'192.0.2.1', '192.0.2.2'}
699
 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
700
 */
701

702
static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
703
{
×
704
  vector<vector<ComboAddress>> candidates;
×
705

706
  if(auto simple = boost::get<iplist_t>(&items)) {
×
707
    vector<ComboAddress> unit = convComboAddressList(*simple, port);
×
708
    candidates.push_back(unit);
×
709
  } else {
×
710
    auto units = boost::get<ipunitlist_t>(items);
×
711
    for(const auto& u : units) {
×
712
      vector<ComboAddress> unit = convComboAddressList(u.second, port);
×
713
      candidates.push_back(unit);
×
714
    }
×
715
  }
×
716
  return candidates;
×
717
}
×
718

719
static vector<string> convStringList(const iplist_t& items)
720
{
×
721
  vector<string> result;
×
722
  result.reserve(items.size());
×
723

724
  for(const auto& item : items) {
×
725
    result.emplace_back(item.second);
×
726
  }
×
727

728
  return result;
×
729
}
×
730

731
static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
732
{
×
733
  vector<pair<int,string> > result;
×
734
  result.reserve(items.size());
×
735

736
  for(const auto& item : items) {
×
737
    result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
×
738
  }
×
739

740
  return result;
×
741
}
×
742

743
bool g_LuaRecordSharedState;
744

745
typedef struct AuthLuaRecordContext
746
{
747
  ComboAddress          bestwho;
748
  DNSName               qname;
749
  DNSZoneRecord         zone_record;
750
  DNSName               zone;
751
} lua_record_ctx_t;
752

753
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
754

755
/*
756
 *  Holds computed hashes for a given entry
757
 */
758
struct EntryHashesHolder
759
{
760
  std::atomic<size_t> weight;
761
  std::string entry;
762
  SharedLockGuarded<std::vector<unsigned int>> hashes;
763
  std::atomic<time_t> lastUsed;
764

765
  EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
×
766
  }
×
767

768
  bool hashesComputed() {
×
769
    return weight == hashes.read_lock()->size();
×
770
  }
×
771
  void hash() {
×
772
    auto locked = hashes.write_lock();
×
773
    locked->clear();
×
774
    locked->reserve(weight);
×
775
    size_t count = 0;
×
776
    while (count < weight) {
×
777
      auto value = boost::str(boost::format("%s-%d") % entry % count);
×
778
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
779
      auto whash = burtle(reinterpret_cast<const unsigned char*>(value.data()), value.size(), 0);
×
780
      locked->push_back(whash);
×
781
      ++count;
×
782
    }
×
783
    std::sort(locked->begin(), locked->end());
×
784
  }
×
785
};
786

787
using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
788

789
static SharedLockGuarded<std::map<
790
  zone_hashes_key_t, // zoneid qname entry
791
  std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
792
  >>
793
s_zone_hashes;
794

795
static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
796

797
/**
798
 * every ~g_luaConsistentHashesCleanupInterval, do a cleanup to delete entries that haven't been used in the last g_luaConsistentHashesExpireDelay
799
 */
800
static void cleanZoneHashes()
801
{
×
802
  auto now = time(nullptr);
×
803
  if (s_lastConsistentHashesCleanup > (now - g_luaConsistentHashesCleanupInterval)) {
×
804
    return ;
×
805
  }
×
806
  s_lastConsistentHashesCleanup = now;
×
807
  std::vector<zone_hashes_key_t> toDelete{};
×
808
  {
×
809
    auto locked = s_zone_hashes.read_lock();
×
810
    auto someTimeAgo = now - g_luaConsistentHashesExpireDelay;
×
811

812
    for (const auto& [key, entry]: *locked) {
×
813
      if (entry->lastUsed > someTimeAgo) {
×
814
        toDelete.push_back(key);
×
815
      }
×
816
    }
×
817
  }
×
818
  if (!toDelete.empty()) {
×
819
    auto wlocked = s_zone_hashes.write_lock();
×
820
    for (const auto& key : toDelete) {
×
821
      wlocked->erase(key);
×
822
    }
×
823
  }
×
824
}
×
825

826
static std::vector<std::shared_ptr<EntryHashesHolder>> getCHashedEntries(const int zoneId, const std::string& queryName, const std::vector<std::pair<int, std::string>>& items)
827
{
×
828
  std::vector<std::shared_ptr<EntryHashesHolder>> result{};
×
829
  std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
×
830

831
  {
×
832
    time_t now = time(nullptr);
×
833
    auto locked = s_zone_hashes.read_lock();
×
834

835
    for (const auto& [weight, entry]: items) {
×
836
      auto key = std::make_tuple(zoneId, queryName, entry);
×
837
      if (locked->count(key) == 0) {
×
838
        newEntries[key] = std::make_shared<EntryHashesHolder>(weight, entry, now);
×
839
      } else {
×
840
        locked->at(key)->weight = weight;
×
841
        locked->at(key)->lastUsed = now;
×
842
        result.push_back(locked->at(key));
×
843
      }
×
844
    }
×
845
  }
×
846
  if (!newEntries.empty()) {
×
847
    auto wlocked = s_zone_hashes.write_lock();
×
848

849
    for (auto& [key, entry]: newEntries) {
×
850
      result.push_back(entry);
×
851
      (*wlocked)[key] = std::move(entry);
×
852
    }
×
853
  }
×
854

855
  return result;
×
856
}
×
857

858
static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
859
{
×
860
  const auto& zoneId = s_lua_record_ctx->zone_record.domain_id;
×
861
  const auto queryName = s_lua_record_ctx->qname.toString();
×
862
  unsigned int sel = std::numeric_limits<unsigned int>::max();
×
863
  unsigned int min = std::numeric_limits<unsigned int>::max();
×
864

865
  boost::optional<std::string> ret;
×
866
  boost::optional<std::string> first;
×
867

868
  cleanZoneHashes();
×
869

870
  auto entries = getCHashedEntries(zoneId, queryName, items);
×
871

872
  ComboAddress::addressOnlyHash addrOnlyHash;
×
873
  auto qhash = addrOnlyHash(bestwho);
×
874
  for (const auto& entry : entries) {
×
875
    if (!entry->hashesComputed()) {
×
876
      entry->hash();
×
877
    }
×
878
    {
×
879
      const auto hashes = entry->hashes.read_lock();
×
880
      if (!hashes->empty()) {
×
881
        if (min > *(hashes->begin())) {
×
882
          min = *(hashes->begin());
×
883
          first = entry->entry;
×
884
        }
×
885

886
        auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
×
887
        if (hash_it != hashes->end()) {
×
888
          if (*hash_it < sel) {
×
889
            sel = *hash_it;
×
890
            ret = entry->entry;
×
891
          }
×
892
        }
×
893
      }
×
894
    }
×
895
  }
×
896
  if (ret != boost::none) {
×
897
    return *ret;
×
898
  }
×
899
  if (first != boost::none) {
×
900
    return *first;
×
901
  }
×
902
  return {};
×
903
}
×
904

905
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)
906
{
×
907
  vector<vector<ComboAddress> > candidates;
×
908
  opts_t opts;
×
909
  if (options) {
×
910
    opts = *options;
×
911
  }
×
912

913
  candidates = convMultiComboAddressList(ips, port);
×
914

915
  bool incompleteCheck{true};
×
916
  for(const auto& unit : candidates) {
×
917
    vector<ComboAddress> available;
×
918
    for(const auto& address : unit) {
×
919
      int status = upcheckf(address, opts);
×
920
      if (status > 0) {
×
921
        available.push_back(address);
×
922
      }
×
923
      if (status >= 0) {
×
924
        incompleteCheck = false;
×
925
      }
×
926
    }
×
927
    if(!available.empty()) {
×
928
      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
929
      return convComboAddressListToString(res);
×
930
    }
×
931
  }
×
932

933
  // All units down or have not completed their checks yet.
934
  if (incompleteCheck) {
×
935
    throw std::runtime_error("if{url,port}up health check has not completed yet");
×
936
  }
×
937

938
  // Apply backupSelector on all candidates
939
  vector<ComboAddress> ret{};
×
940
  for(const auto& unit : candidates) {
×
941
    ret.insert(ret.end(), unit.begin(), unit.end());
×
942
  }
×
943

944
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
×
945
  return convComboAddressListToString(res);
×
946
}
×
947

948
// Lua functions available to the user
949

950
static string lua_latlon()
951
{
×
952
  double lat{0};
×
953
  double lon{0};
×
954
  getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
×
955
  return std::to_string(lat)+" "+std::to_string(lon);
×
956
}
×
957

958
static string lua_latlonloc()
959
{
×
960
  string loc;
×
961
  getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
×
962
  return loc;
×
963
}
×
964

965
static string lua_closestMagic()
966
{
×
967
  vector<ComboAddress> candidates;
×
968
  // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
969
  for (auto label : s_lua_record_ctx->qname.getRawLabels()) {
×
970
    std::replace(label.begin(), label.end(), '-', '.');
×
971
    try {
×
972
      candidates.emplace_back(label);
×
973
    } catch (const PDNSException& exc) {
×
974
      // no need to continue as we most likely reached the end of the ip list
975
      break ;
×
976
    }
×
977
  }
×
978
  return pickclosest(s_lua_record_ctx->bestwho, candidates).toString();
×
979
}
×
980

981
static string lua_latlonMagic()
982
{
×
983
  auto labels = s_lua_record_ctx->qname.getRawLabels();
×
984
  if (labels.size() < 4) {
×
985
    return {"unknown"};
×
986
  }
×
987
  double lat{0};
×
988
  double lon{0};
×
989
  getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
×
990
  return std::to_string(lat)+" "+std::to_string(lon);
×
991
}
×
992

993
static string lua_createReverse(const string &format, boost::optional<opts_t> exceptions)
994
{
×
995
  try {
×
996
    auto labels = s_lua_record_ctx->qname.getRawLabels();
×
997
    if (labels.size() < 4) {
×
998
      return {"unknown"};
×
999
    }
×
1000

1001
    vector<ComboAddress> candidates;
×
1002

1003
    // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
1004
    // exceptions["1.2.3.4"]="bert.powerdns.com" then provides an exception
1005
    if (exceptions) {
×
1006
      ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
×
1007
      const auto& uom = *exceptions;
×
1008
      for (const auto& address : uom) {
×
1009
        if(ComboAddress(address.first, 0) == req) {
×
1010
          return address.second;
×
1011
        }
×
1012
      }
×
1013
    }
×
1014
    boost::format fmt(format);
×
1015
    fmt.exceptions(boost::io::all_error_bits ^ (boost::io::too_many_args_bit | boost::io::too_few_args_bit));
×
1016
    fmt % labels[3] % labels[2] % labels[1] % labels[0];
×
1017

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

1020
    boost::format fmt2("%02x%02x%02x%02x");
×
1021
    for (int i = 3; i >= 0; --i) {
×
1022
      fmt2 % atoi(labels[i].c_str());
×
1023
    }
×
1024

1025
    fmt % (fmt2.str());
×
1026

1027
    return fmt.str();
×
1028
  }
×
1029
  catch(std::exception& ex) {
×
1030
    g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
×
1031
  }
×
1032
  return {"error"};
×
1033
}
×
1034

1035
static string lua_createForward()
1036
{
×
1037
  static string allZerosIP{"0.0.0.0"};
×
1038
  try {
×
1039
    DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
1040
    if (!record_name.isWildcard()) {
×
1041
      return allZerosIP;
×
1042
    }
×
1043
    record_name.chopOff();
×
1044
    DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
1045

1046
    // parts is something like ["1", "2", "3", "4", "static"] or
1047
    // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
1048
    auto parts = rel.getRawLabels();
×
1049
    // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
1050
    if (parts.size() >= 4) {
×
1051
      ComboAddress address(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
×
1052
      return address.toString();
×
1053
    }
×
1054
    if (!parts.empty()) {
×
1055
      auto& input = parts.at(0);
×
1056

1057
      // allow a word without - in front, as long as it does not contain anything that could be a number
1058
      size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
×
1059
      if (nonhexprefix > 0) {
×
1060
        input = input.substr(nonhexprefix);
×
1061
      }
×
1062

1063
      // either hex string, or 12-13-14-15
1064
      vector<string> ip_parts;
×
1065

1066
      stringtok(ip_parts, input, "-");
×
1067
      if (ip_parts.size() >= 4) {
×
1068
        // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
1069
        string ret;
×
1070
        for (size_t index=4; index > 0; index--) {
×
1071
          auto octet = ip_parts.at(ip_parts.size() - index);
×
1072
          auto octetVal = std::stol(octet); // may throw
×
1073
          if (octetVal >= 0 && octetVal <= 255) {
×
1074
            ret += octet + ".";
×
1075
          } else {
×
1076
            return allZerosIP;
×
1077
          }
×
1078
        }
×
1079
        ret.resize(ret.size() - 1); // remove trailing dot after last octet
×
1080
        return ret;
×
1081
      }
×
1082
      if (input.length() >= 8) {
×
1083
        auto last8 = input.substr(input.length()-8);
×
1084
        unsigned int part1{0};
×
1085
        unsigned int part2{0};
×
1086
        unsigned int part3{0};
×
1087
        unsigned int part4{0};
×
1088
        if (sscanf(last8.c_str(), "%02x%02x%02x%02x", &part1, &part2, &part3, &part4) == 4) {
×
1089
          ComboAddress address(std::to_string(part1) + "." + std::to_string(part2) + "." + std::to_string(part3) + "." + std::to_string(part4));
×
1090
          return address.toString();
×
1091
        }
×
1092
      }
×
1093
    }
×
1094
    return allZerosIP;
×
1095
  } catch (const PDNSException &e) {
×
1096
    return allZerosIP;
×
1097
  }
×
1098
}
×
1099

1100
static string lua_createForward6()
1101
{
×
1102
   static string allZerosIP{"::"};
×
1103
   try {
×
1104
     DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
1105
     if (!record_name.isWildcard()) {
×
1106
       return allZerosIP;
×
1107
     }
×
1108
     record_name.chopOff();
×
1109
     DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
1110

1111
     auto parts = rel.getRawLabels();
×
1112
     if (parts.size() == 8) {
×
1113
       string tot;
×
1114
       for (int chunk = 0; chunk < 8; ++chunk) {
×
1115
         if (chunk != 0) {
×
1116
           tot.append(1, ':');
×
1117
         }
×
1118
        tot += parts.at(chunk);
×
1119
      }
×
1120
      ComboAddress address(tot);
×
1121
      return address.toString();
×
1122
    }
×
1123
    if (parts.size() == 1) {
×
1124
      if (parts[0].find('-') != std::string::npos) {
×
1125
        std::replace(parts[0].begin(), parts[0].end(), '-', ':');
×
1126
        ComboAddress address(parts[0]);
×
1127
        return address.toString();
×
1128
      }
×
1129
      if (parts[0].size() >= 32) {
×
1130
        auto ippart = parts[0].substr(parts[0].size()-32);
×
1131
        auto fulladdress =
×
1132
          ippart.substr(0, 4) + ":" +
×
1133
          ippart.substr(4, 4) + ":" +
×
1134
          ippart.substr(8, 4) + ":" +
×
1135
          ippart.substr(12, 4) + ":" +
×
1136
          ippart.substr(16, 4) + ":" +
×
1137
          ippart.substr(20, 4) + ":" +
×
1138
          ippart.substr(24, 4) + ":" +
×
1139
          ippart.substr(28, 4);
×
1140

1141
        ComboAddress address(fulladdress);
×
1142
        return address.toString();
×
1143
      }
×
1144
    }
×
1145
    return allZerosIP;
×
1146
  } catch (const PDNSException &e) {
×
1147
    return allZerosIP;
×
1148
  }
×
1149
}
×
1150

1151
static string lua_createReverse6(const string &format, boost::optional<opts_t> exceptions)
1152
{
×
1153
  vector<ComboAddress> candidates;
×
1154

1155
  try {
×
1156
    auto labels= s_lua_record_ctx->qname.getRawLabels();
×
1157
    if (labels.size()<32) {
×
1158
      return {"unknown"};
×
1159
    }
×
1160

1161
    boost::format fmt(format);
×
1162
    fmt.exceptions(boost::io::all_error_bits ^ (boost::io::too_many_args_bit | boost::io::too_few_args_bit));
×
1163

1164
    string together;
×
1165
    vector<string> quads;
×
1166
    for (int chunk = 0; chunk < 8; ++chunk) {
×
1167
      if (chunk != 0) {
×
1168
        together += ":";
×
1169
      }
×
1170
      string lquad;
×
1171
      for (int quartet = 0; quartet < 4; ++quartet) {
×
1172
        lquad.append(1, labels[31 - chunk * 4 - quartet][0]);
×
1173
        together += labels[31 - chunk * 4 - quartet][0];
×
1174
      }
×
1175
      quads.push_back(lquad);
×
1176
    }
×
1177
    ComboAddress ip6(together,0);
×
1178

1179
    if (exceptions) {
×
1180
      auto& addrs=*exceptions;
×
1181
      for(const auto& addr: addrs) {
×
1182
        // this makes sure we catch all forms of the address
1183
        if (ComboAddress(addr.first, 0) == ip6) {
×
1184
          return addr.second;
×
1185
        }
×
1186
      }
×
1187
    }
×
1188

1189
    string dashed=ip6.toString();
×
1190
    std::replace(dashed.begin(), dashed.end(), ':', '-');
×
1191

1192
    // https://github.com/PowerDNS/pdns/issues/7524
1193
    if (boost::ends_with(dashed, "-")) {
×
1194
      // "a--a-" -> "a--a-0"
1195
      dashed.push_back('0');
×
1196
    }
×
1197
    if (boost::starts_with(dashed, "-") || dashed.compare(2, 2, "--") == 0) {
×
1198
      // "-a--a" -> "0-a--a"               "aa--a" -> "0aa--a"
1199
      dashed.insert(0, "0");
×
1200
    }
×
1201

1202
    for (int byte = 31; byte >= 0; --byte) {
×
1203
      fmt % labels[byte];
×
1204
    }
×
1205
    fmt % dashed;
×
1206

1207
    for(const auto& lquad : quads) {
×
1208
      fmt % lquad;
×
1209
    }
×
1210

1211
    return fmt.str();
×
1212
  }
×
1213
  catch(std::exception& ex) {
×
1214
    g_log<<Logger::Error<<"Lua record exception: "<<ex.what()<<endl;
×
1215
  }
×
1216
  catch(PDNSException& ex) {
×
1217
    g_log<<Logger::Error<<"Lua record exception: "<<ex.reason<<endl;
×
1218
  }
×
1219
  return {"unknown"};
×
1220
}
×
1221

1222
static vector<string> lua_filterForward(const string& address, NetmaskGroup& nmg, boost::optional<string> fallback)
1223
{
×
1224
  ComboAddress caddr(address);
×
1225

1226
  if (nmg.match(ComboAddress(address))) {
×
1227
    return {address};
×
1228
  }
×
1229
  if (fallback) {
×
1230
    if (fallback->empty()) {
×
1231
      // if fallback is an empty string, return an empty array
1232
      return {};
×
1233
    }
×
1234
    return {*fallback};
×
1235
  }
×
1236

1237
  if (caddr.isIPv4()) {
×
1238
    return {string("0.0.0.0")};
×
1239
  }
×
1240
  return {"::"};
×
1241
}
×
1242

1243
/*
1244
 * Simplistic test to see if an IP address listens on a certain port
1245
 * Will return a single IP address from the set of available IP addresses. If
1246
 * no IP address is available, will return a random element of the set of
1247
 * addresses supplied for testing.
1248
 *
1249
 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
1250
 */
1251
static vector<string> lua_ifportup(int port, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options)
1252
{
×
1253
  port = std::max(port, 0);
×
1254
  port = std::min(port, static_cast<int>(std::numeric_limits<uint16_t>::max()));
×
1255

1256
  auto checker = [](const ComboAddress& addr, const opts_t& opts) -> int {
×
1257
    return g_up.isUp(addr, opts);
×
1258
  };
×
1259
  return genericIfUp(ips, std::move(options), checker, port);
×
1260
}
×
1261

1262
static vector<string> lua_ifurlextup(const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options)
1263
{
×
1264
  vector<ComboAddress> candidates;
×
1265
  opts_t opts;
×
1266
  if (options) {
×
1267
    opts = *options;
×
1268
  }
×
1269

1270
  ComboAddress ca_unspec;
×
1271
  ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1272

1273
  // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1274
  bool incompleteCheck{true};
×
1275
  for (const auto& [count, unitmap] : ipurls) {
×
1276
    // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1277
    vector<ComboAddress> available;
×
1278

1279
    for (const auto& [ipStr, url] : unitmap) {
×
1280
      // unit: ["192.0.2.1"] = "https://example.com"
1281
      ComboAddress address(ipStr);
×
1282
      candidates.push_back(address);
×
1283
      int status = g_up.isUp(ca_unspec, url, opts);
×
1284
      if (status > 0) {
×
1285
        available.push_back(address);
×
1286
      }
×
1287
      if (status >= 0) {
×
1288
        incompleteCheck = false;
×
1289
      }
×
1290
    }
×
1291
    if(!available.empty()) {
×
1292
      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
1293
      return convComboAddressListToString(res);
×
1294
    }
×
1295
  }
×
1296

1297
  // All units down or have not completed their checks yet.
1298
  if (incompleteCheck) {
×
1299
    throw std::runtime_error("ifexturlup health check has not completed yet");
×
1300
  }
×
1301

1302
  // Apply backupSelector on all candidates
1303
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
×
1304
  return convComboAddressListToString(res);
×
1305
}
×
1306

1307
static vector<string> lua_ifurlup(const std::string& url, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options)
1308
{
×
1309
  auto checker = [&url](const ComboAddress& addr, const opts_t& opts) -> int {
×
1310
    return g_up.isUp(addr, url, opts);
×
1311
  };
×
1312
  return genericIfUp(ips, std::move(options), checker);
×
1313
}
×
1314

1315
/*
1316
 * Returns a random IP address from the supplied list
1317
 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
1318
 */
1319
static string lua_pickrandom(const iplist_t& ips)
1320
{
×
1321
  vector<string> items = convStringList(ips);
×
1322
  return pickRandom<string>(items);
×
1323
}
×
1324

1325
/*
1326
 * Based on the hash of `bestwho`, returns an IP address from the list
1327
 * supplied, weighted according to the results of isUp calls.
1328
 * @example pickselfweighted('http://example.com/weight', { "192.0.2.20", "203.0.113.4", "203.0.113.2" })
1329
 */
1330
static string lua_pickselfweighted(const std::string& url, const iplist_t& ips, boost::optional<opts_t> options)
1331
{
×
1332
  vector< pair<int, ComboAddress> > items;
×
1333
  opts_t opts;
×
1334
  if(options) {
×
1335
    opts = *options;
×
1336
  }
×
1337

1338
  items.reserve(ips.capacity());
×
1339
  bool available = false;
×
1340

1341
  vector<ComboAddress> conv = convComboAddressList(ips);
×
1342
  for (auto& entry : conv) {
×
1343
    int weight = 0;
×
1344
    weight = g_up.isUp(entry, url, opts);
×
1345
    if(weight>0) {
×
1346
      available = true;
×
1347
    }
×
1348
    items.emplace_back(weight, entry);
×
1349
  }
×
1350
  if(available) {
×
1351
    return pickWeightedHashed<ComboAddress>(s_lua_record_ctx->bestwho, items).toString();
×
1352
  }
×
1353

1354
  // All units down, apply backupSelector on all candidates
1355
  return pickWeightedRandom<ComboAddress>(items).toString();
×
1356
}
×
1357

1358
static vector<string> lua_pickrandomsample(int n, const iplist_t& ips)
1359
{
×
1360
  vector<string> items = convStringList(ips);
×
1361
  return pickRandomSample<string>(n, items);
×
1362
}
×
1363

1364
static string lua_pickhashed(const iplist_t& ips)
1365
{
×
1366
  vector<string> items = convStringList(ips);
×
1367
  return pickHashed<string>(s_lua_record_ctx->bestwho, items);
×
1368
}
×
1369

1370
/*
1371
 * Returns a random IP address from the supplied list, as weighted by the
1372
 * various ``weight`` parameters
1373
 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1374
 */
1375
static string lua_pickwrandom(const std::unordered_map<int, wiplist_t>& ips)
1376
{
×
1377
  vector< pair<int, string> > items = convIntStringPairList(ips);
×
1378
  return pickWeightedRandom<string>(items);
×
1379
}
×
1380

1381
/*
1382
 * Based on the hash of `bestwho`, returns an IP address from the list
1383
 * supplied, as weighted by the various `weight` parameters
1384
 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1385
 */
1386
static string lua_pickwhashed(std::unordered_map<int, wiplist_t> ips)
1387
{
×
1388
  vector< pair<int, string> > items;
×
1389

1390
  items.reserve(ips.size());
×
1391
  for (auto& entry : ips) {
×
1392
    items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
×
1393
  }
×
1394

1395
  return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1396
}
×
1397

1398
/*
1399
 * Based on the hash of the record name, return an IP address from the list
1400
 * supplied, as weighted by the various `weight` parameters
1401
 * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1402
 */
1403
static string lua_picknamehashed(std::unordered_map<int, wiplist_t> ips)
1404
{
×
1405
  vector< pair<int, string> > items;
×
1406

1407
  items.reserve(ips.size());
×
1408
  for (auto& address : ips) {
×
1409
    items.emplace_back(atoi(address.second[1].c_str()), address.second[2]);
×
1410
  }
×
1411

1412
  return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
×
1413
}
×
1414

1415
/*
1416
 * Based on the hash of `bestwho`, returns an IP address from the list
1417
 * supplied, as weighted by the various `weight` parameters and distributed consistently
1418
 * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1419
 */
1420
static string lua_pickchashed(const std::unordered_map<int, wiplist_t>& ips)
1421
{
×
1422
  std::vector<std::pair<int, std::string>> items;
×
1423

1424
  items.reserve(ips.size());
×
1425
  for (const auto& entry : ips) {
×
1426
    items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
×
1427
  }
×
1428

1429
  return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1430
}
×
1431

1432
static string lua_pickclosest(const iplist_t& ips)
1433
{
×
1434
  vector<ComboAddress> conv = convComboAddressList(ips);
×
1435

1436
  return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1437
}
×
1438

1439
static void lua_report(const string& /* event */, const boost::optional<string>& /* line */)
1440
{
×
1441
  throw std::runtime_error("Script took too long");
×
1442
}
×
1443

1444
static string lua_geoiplookup(const string &address, const GeoIPInterface::GeoIPQueryAttribute attr)
1445
{
×
1446
  return getGeo(address, attr);
×
1447
}
×
1448

1449
using combovar_t = const boost::variant<string,vector<pair<int,string> > >;
1450

1451
static bool lua_asnum(const combovar_t& asns)
1452
{
×
1453
  string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
×
1454
  return doCompare(asns, res, [](const std::string& arg1, const std::string& arg2) -> bool {
×
1455
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
×
1456
    });
×
1457
}
×
1458

1459
static bool lua_continent(const combovar_t& continent)
1460
{
×
1461
  string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1462
  return doCompare(continent, res, [](const std::string& arg1, const std::string& arg2) -> bool {
×
1463
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
×
1464
    });
×
1465
}
×
1466

1467
static string lua_continentCode()
1468
{
×
1469
  string unknown("unknown");
×
1470
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1471
  if ( res == unknown ) {
×
1472
   return {"--"};
×
1473
  }
×
1474
  return res;
×
1475
}
×
1476

1477
static bool lua_country(const combovar_t& var)
1478
{
×
1479
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1480
  return doCompare(var, res, [](const std::string& arg1, const std::string& arg2) -> bool {
×
1481
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
×
1482
    });
×
1483

1484
}
×
1485

1486
static string lua_countryCode()
1487
{
×
1488
  string unknown("unknown");
×
1489
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1490
  if (res == unknown) {
×
1491
   return {"--"};
×
1492
  }
×
1493
  return res;
×
1494
}
×
1495

1496
static bool lua_region(const combovar_t& var)
1497
{
×
1498
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1499
  return doCompare(var, res, [](const std::string& arg1, const std::string& arg2) -> bool {
×
1500
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
×
1501
    });
×
1502

1503
}
×
1504

1505
static string lua_regionCode()
1506
{
×
1507
  string unknown("unknown");
×
1508
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1509
  if ( res == unknown ) {
×
1510
   return {"--"};
×
1511
  }
×
1512
  return res;
×
1513
}
×
1514

1515
static bool lua_netmask(const iplist_t& ips)
1516
{
×
1517
  for (const auto& addr : ips) {
×
1518
    Netmask netmask(addr.second);
×
1519
    if (netmask.match(s_lua_record_ctx->bestwho)) {
×
1520
      return true;
×
1521
    }
×
1522
  }
×
1523
  return false;
×
1524
}
×
1525

1526
/* {
1527
     {
1528
      {'192.168.0.0/16', '10.0.0.0/8'},
1529
      {'192.168.20.20', '192.168.20.21'}
1530
     },
1531
     {
1532
      {'0.0.0.0/0'}, {'192.0.2.1'}
1533
     }
1534
   }
1535
*/
1536
static string lua_view(const vector<pair<int, vector<pair<int, iplist_t> > > >& pairs)
1537
{
×
1538
  for(const auto& rule : pairs) {
×
1539
    const auto& netmasks=rule.second[0].second;
×
1540
    const auto& destinations=rule.second[1].second;
×
1541
    for(const auto& nmpair : netmasks) {
×
1542
      Netmask netmask(nmpair.second);
×
1543
      if (netmask.match(s_lua_record_ctx->bestwho)) {
×
1544
        if (destinations.empty()) {
×
1545
          throw std::invalid_argument("The IP list cannot be empty (for netmask " + netmask.toString() + ")");
×
1546
        }
×
1547
        return destinations[dns_random(destinations.size())].second;
×
1548
      }
×
1549
    }
×
1550
  }
×
1551
  return {};
×
1552
}
×
1553

1554
static vector<string> lua_all(const vector< pair<int,string> >& ips)
1555
{
×
1556
  vector<string> result;
×
1557
  result.reserve(ips.size());
×
1558

1559
  for (const auto& address : ips) {
×
1560
      result.emplace_back(address.second);
×
1561
  }
×
1562
  if(result.empty()) {
×
1563
    throw std::invalid_argument("The IP list cannot be empty");
×
1564
  }
×
1565
  return result;
×
1566
}
×
1567

1568
static vector<string> lua_dblookup(const string& record, uint16_t qtype)
1569
{
×
1570
  ZoneName rec;
×
1571
  vector<string> ret;
×
1572
  try {
×
1573
    rec = ZoneName(record);
×
1574
  }
×
1575
  catch (const std::exception& e) {
×
1576
    g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
×
1577
    return ret;
×
1578
  }
×
1579
  try {
×
1580
    SOAData soaData;
×
1581

1582
    if (!getAuth(rec, qtype, &soaData)) {
×
1583
      return ret;
×
1584
    }
×
1585

NEW
1586
    vector<DNSZoneRecord> drs = lookup(rec.operator const DNSName&(), qtype, soaData.domain_id);
×
1587
    for (const auto& drec : drs) {
×
1588
      ret.push_back(drec.dr.getContent()->getZoneRepresentation());
×
1589
    }
×
1590
  }
×
1591
  catch (std::exception& e) {
×
1592
    g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
×
1593
  }
×
1594
  return ret;
×
1595
}
×
1596

1597
static void lua_include(LuaContext& lua, const string& record)
1598
{
×
1599
  DNSName rec;
×
1600
  try {
×
1601
    rec = DNSName(record) + s_lua_record_ctx->zone;
×
1602
  } catch (const std::exception &e){
×
1603
    g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl;
×
1604
    return;
×
1605
  }
×
1606
  try {
×
1607
    vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zone_record.domain_id);
×
1608
    for(const auto& zonerecord : drs) {
×
1609
      auto luarecord = getRR<LUARecordContent>(zonerecord.dr);
×
1610
      lua.executeCode(luarecord->getCode());
×
1611
    }
×
1612
  }
×
1613
  catch(std::exception& e) {
×
1614
    g_log<<Logger::Error<<"Failed to load include record for Lua record "<<rec<<": "<<e.what()<<endl;
×
1615
  }
×
1616
}
×
1617

1618
// Lua variables available to the user
1619

1620
static std::unordered_map<std::string, int> lua_variables{
1621
  {"ASn", GeoIPInterface::GeoIPQueryAttribute::ASn},
1622
  {"City", GeoIPInterface::GeoIPQueryAttribute::City},
1623
  {"Continent", GeoIPInterface::GeoIPQueryAttribute::Continent},
1624
  {"Country", GeoIPInterface::GeoIPQueryAttribute::Country},
1625
  {"Country2", GeoIPInterface::GeoIPQueryAttribute::Country2},
1626
  {"Name", GeoIPInterface::GeoIPQueryAttribute::Name},
1627
  {"Region", GeoIPInterface::GeoIPQueryAttribute::Region},
1628
  {"Location", GeoIPInterface::GeoIPQueryAttribute::Location}
1629
};
1630

1631
static void setupLuaRecords(LuaContext& lua)
1632
{
×
1633
  lua.writeFunction("report", [](const string& event, const boost::optional<string>& line) -> void {
×
1634
      lua_report(event, line);
×
1635
    });
×
1636

1637
  if (g_luaRecordExecLimit > 0) {
×
1638
    lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1639
  }
×
1640

1641
  lua.writeFunction("latlon", []() -> string {
×
1642
      return lua_latlon();
×
1643
    });
×
1644
  lua.writeFunction("latlonloc", []() -> string {
×
1645
      return lua_latlonloc();
×
1646
    });
×
1647
  lua.writeFunction("closestMagic", []() -> string {
×
1648
      return lua_closestMagic();
×
1649
    });
×
1650
  lua.writeFunction("latlonMagic", []()-> string {
×
1651
      return lua_latlonMagic();
×
1652
    });
×
1653

1654
  lua.writeFunction("createForward", []() -> string {
×
1655
      return lua_createForward();
×
1656
    });
×
1657
  lua.writeFunction("createForward6", []() -> string {
×
1658
      return lua_createForward6();
×
1659
    });
×
1660

1661
  lua.writeFunction("createReverse", [](const string &format, boost::optional<opts_t> exceptions) -> string {
×
1662
      return lua_createReverse(format, std::move(exceptions));
×
1663
    });
×
1664
  lua.writeFunction("createReverse6", [](const string &format, boost::optional<opts_t> exceptions) -> string {
×
1665
      return lua_createReverse6(format, std::move(exceptions));
×
1666
    });
×
1667

1668
  lua.writeFunction("filterForward", [](const string& address, NetmaskGroup& nmg, boost::optional<string> fallback) -> vector<string> {
×
1669
      return lua_filterForward(address, nmg, std::move(fallback));
×
1670
    });
×
1671

1672
  lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options) -> vector<string> {
×
1673
      return lua_ifportup(port, ips, std::move(options));
×
1674
    });
×
1675

1676
  lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) -> vector<string> {
×
1677
      return lua_ifurlextup(ipurls, std::move(options));
×
1678
    });
×
1679

1680
  lua.writeFunction("ifurlup", [](const std::string& url, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options) -> vector<string> {
×
1681
      return lua_ifurlup(url, ips, std::move(options));
×
1682
    });
×
1683

1684
  lua.writeFunction("pickrandom", [](const iplist_t& ips) -> string {
×
1685
      return lua_pickrandom(ips);
×
1686
    });
×
1687

1688
  lua.writeFunction("pickselfweighted", [](const std::string& url, const iplist_t& ips, boost::optional<opts_t> options) -> string {
×
1689
      return lua_pickselfweighted(url, ips, std::move(options));
×
1690
    });
×
1691

1692
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) -> vector<string> {
×
1693
      return lua_pickrandomsample(n, ips);
×
1694
    });
×
1695

1696
  lua.writeFunction("pickhashed", [](const iplist_t& ips) -> string {
×
1697
      return lua_pickhashed(ips);
×
1698
    });
×
1699
  lua.writeFunction("pickwrandom", [](const std::unordered_map<int, wiplist_t>& ips) -> string {
×
1700
      return lua_pickwrandom(ips);
×
1701
    });
×
1702

1703
  lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t> ips) -> string {
×
1704
      return lua_pickwhashed(std::move(ips));
×
1705
    });
×
1706

1707
  lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t> ips) -> string {
×
1708
      return lua_picknamehashed(std::move(ips));
×
1709
    });
×
1710
  lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) -> string {
×
1711
      return lua_pickchashed(ips);
×
1712
    });
×
1713

1714
  lua.writeFunction("pickclosest", [](const iplist_t& ips) -> string {
×
1715
      return lua_pickclosest(ips);
×
1716
    });
×
1717

1718
  lua.writeFunction("geoiplookup", [](const string &address, const GeoIPInterface::GeoIPQueryAttribute attr) -> string {
×
1719
      return lua_geoiplookup(address, attr);
×
1720
    });
×
1721

1722
  lua.writeFunction("asnum", [](const combovar_t& asns) -> bool {
×
1723
      return lua_asnum(asns);
×
1724
    });
×
1725
  lua.writeFunction("continent", [](const combovar_t& continent) -> bool {
×
1726
      return lua_continent(continent);
×
1727
    });
×
1728
  lua.writeFunction("continentCode", []() -> string {
×
1729
      return lua_continentCode();
×
1730
    });
×
1731
  lua.writeFunction("country", [](const combovar_t& var) -> bool {
×
1732
      return lua_country(var);
×
1733
    });
×
1734
  lua.writeFunction("countryCode", []() -> string {
×
1735
      return lua_countryCode();
×
1736
    });
×
1737
  lua.writeFunction("region", [](const combovar_t& var) -> bool {
×
1738
      return lua_region(var);
×
1739
    });
×
1740
  lua.writeFunction("regionCode", []() -> string {
×
1741
      return lua_regionCode();
×
1742
    });
×
1743
  lua.writeFunction("netmask", [](const iplist_t& ips) -> bool {
×
1744
      return lua_netmask(ips);
×
1745
    });
×
1746
  lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& pairs) -> string {
×
1747
      return lua_view(pairs);
×
1748
    });
×
1749

1750
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) -> vector<string> {
×
1751
      return lua_all(ips);
×
1752
    });
×
1753

1754
  lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) -> vector<string> {
×
1755
      return lua_dblookup(record, qtype);
×
1756
    });
×
1757

1758
  lua.writeFunction("include", [&lua](const string& record) -> void {
×
1759
      lua_include(lua, record);
×
1760
    });
×
1761

1762
  lua.writeVariable("GeoIPQueryAttribute", lua_variables);
×
1763
}
×
1764

1765
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)
1766
{
×
1767
  if(!LUA ||                  // we don't have a Lua state yet
×
1768
     !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1769
    LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
×
1770
    setupLuaRecords(*LUA->getLua());
×
1771
  }
×
1772

1773
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1774

1775
  LuaContext& lua = *LUA->getLua();
×
1776

1777
  s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1778
  s_lua_record_ctx->qname = query;
×
1779
  s_lua_record_ctx->zone_record = zone_record;
×
1780
  s_lua_record_ctx->zone = zone;
×
1781

1782
  lua.writeVariable("qname", query);
×
1783
  lua.writeVariable("zone", zone);
×
1784
  lua.writeVariable("zoneid", zone_record.domain_id);
×
1785
  lua.writeVariable("who", dnsp.getInnerRemote());
×
1786
  lua.writeVariable("localwho", dnsp.getLocal());
×
1787
  lua.writeVariable("dh", (dnsheader*)&dnsp.d);
×
1788
  lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
×
1789
  lua.writeVariable("tcp", dnsp.d_tcp);
×
1790
  lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
×
1791
  if(dnsp.hasEDNSSubnet()) {
×
1792
    lua.writeVariable("ecswho", dnsp.getRealRemote());
×
1793
    s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
×
1794
  }
×
1795
  else {
×
1796
    lua.writeVariable("ecswho", nullptr);
×
1797
    s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
×
1798
  }
×
1799
  lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
×
1800

1801
  try {
×
1802
    string actual;
×
1803
    if(!code.empty() && code[0]!=';')
×
1804
      actual = "return " + code;
×
1805
    else
×
1806
      actual = code.substr(1);
×
1807

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

1810
    vector<string> contents;
×
1811
    if(auto str = boost::get<string>(&content))
×
1812
      contents.push_back(*str);
×
1813
    else
×
1814
      for(const auto& c : boost::get<vector<pair<int,string>>>(content))
×
1815
        contents.push_back(c.second);
×
1816

1817
    for(const auto& content_it: contents) {
×
1818
      if(qtype==QType::TXT)
×
1819
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
×
1820
      else
×
1821
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
×
1822
    }
×
1823
  } catch(std::exception &e) {
×
1824
    g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
×
1825
    try {
×
1826
      std::rethrow_if_nested(e);
×
1827
      g_log<<endl;
×
1828
    } catch(const std::exception& ne) {
×
1829
      g_log << ": " << ne.what() << std::endl;
×
1830
    }
×
1831
    catch(const PDNSException& ne) {
×
1832
      g_log << ": " << ne.reason << std::endl;
×
1833
    }
×
1834
    throw ;
×
1835
  }
×
1836

1837
  return ret;
×
1838
}
×
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