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

PowerDNS / pdns / 19332892815

13 Nov 2025 01:18PM UTC coverage: 73.029% (-0.04%) from 73.072%
19332892815

Pull #16389

github

web-flow
Merge bd30b08e1 into 60476c10b
Pull Request #16389: auth Lua health checks: more responsiveness

38533 of 63446 branches covered (60.73%)

Branch coverage included in aggregate %.

0 of 13 new or added lines in 1 file covered. (0.0%)

175 existing lines in 14 files now uncovered.

128003 of 164594 relevant lines covered (77.77%)

6070740.47 hits per line

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

0.18
/pdns/lua-record.cc
1
#include <algorithm>
2
#include <condition_variable>
3
#include <future>
4
#include <random>
5
#include <thread>
6
#include <tuple>
7
#include <utility>
8
#include <boost/format.hpp>
9
#include <boost/uuid/string_generator.hpp>
10

11
#include "qtype.hh"
12
#include "version.hh"
13
#include "ext/luawrapper/include/LuaContext.hpp"
14
#include "lock.hh"
15
#include "lua-auth4.hh"
16
#include "sstuff.hh"
17
#include "minicurl.hh"
18
#include "ueberbackend.hh"
19
#include "dns_random.hh"
20
#include "auth-main.hh"
21
#include "../modules/geoipbackend/geoipinterface.hh" // only for the enum
22

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

26
   investigate IPv6
27

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

30
   ponder ECS scopemask setting
31

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

34
   add attribute for certificate check in genericIfUp
35

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

39
   pool of UeberBackends?
40

41
   Pool checks ?
42
 */
43

44
extern int  g_luaRecordExecLimit;
45

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

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

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

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

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

104
    string remstring;
×
105
    try {
×
106
      int timeout = 2;
×
107
      if (cd.opts.count("timeout")) {
×
108
        timeout = std::atoi(cd.opts.at("timeout").c_str());
×
109
      }
×
110
      string useragent = productName();
×
111
      if (cd.opts.count("useragent")) {
×
112
        useragent = cd.opts.at("useragent");
×
113
      }
×
114
      size_t byteslimit = 0;
×
115
      if (cd.opts.count("byteslimit")) {
×
116
        byteslimit = static_cast<size_t>(std::atoi(cd.opts.at("byteslimit").c_str()));
×
117
      }
×
118
      int http_code = 200;
×
119
      if (cd.opts.count("httpcode") != 0) {
×
120
        http_code = pdns::checked_stoi<int>(cd.opts.at("httpcode"));
×
121
      }
×
122

123
      MiniCurl minicurl(useragent, false);
×
124

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

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

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

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

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

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

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

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

264
      // Wait for at most one complete check interval, but allow an earlier
265
      // wakeup in case more work is being put in d_statuses.
NEW
266
      {
×
NEW
267
        std::unique_lock<std::mutex> lock(d_mutex);
×
NEW
268
        auto sleepTime = std::chrono::seconds(g_luaHealthChecksInterval) - (std::chrono::system_clock::now() - checkStart);
×
NEW
269
        if (sleepTime > std::chrono::seconds::zero()) {
×
NEW
270
          d_condvar.wait_until(lock, std::chrono::system_clock::now() + std::chrono::seconds(g_luaHealthChecksInterval));
×
NEW
271
        }
×
NEW
272
      }
×
273
    }
×
274
  }
×
275

276
  typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
277
  SharedLockGuarded<statuses_t> d_statuses;
278

279
  std::unique_ptr<std::thread> d_checkerThread;
280
  std::atomic_flag d_checkerThreadStarted;
281

282
  std::mutex d_mutex; // used with the condition variable below
283
  std::condition_variable d_condvar;
284

285
  void setStatus(const CheckDesc& cd, bool status)
286
  {
×
287
    auto statuses = d_statuses.write_lock();
×
288
    auto& state = (*statuses)[cd];
×
289
    state->lastStatusUpdate = time(nullptr);
×
290
    state->first = false;
×
291
    if (status) {
×
292
      state->failures = 0;
×
293
      state->status = true;
×
294
    } else {
×
295
      unsigned int minimumFailures = 1;
×
296
      if (cd.opts.count("minimumFailures") != 0) {
×
297
        unsigned int value = std::atoi(cd.opts.at("minimumFailures").c_str());
×
298
        if (value != 0) {
×
299
          minimumFailures = std::max(minimumFailures, value);
×
300
        }
×
301
      }
×
302
      // Since `status' was set to false at constructor time, we need to
303
      // recompute its value unconditionally to expose "down, but not enough
304
      // times yet" targets as up.
305
      state->status = ++state->failures < minimumFailures;
×
306
    }
×
307
  }
×
308

309
  //NOLINTNEXTLINE(readability-identifier-length)
310
  void setWeight(const CheckDesc& cd, int weight){
×
311
    auto statuses = d_statuses.write_lock();
×
312
    auto& state = (*statuses)[cd];
×
313
    state->weight = weight;
×
314
  }
×
315

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

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

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

379
int IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts)
NEW
380
{
×
NEW
381
  CheckDesc cd{remote, "", opts};
×
382
  return isUp(cd);
×
383
}
×
384

385
int IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts)
386
{
×
387
  CheckDesc cd{remote, url, opts};
×
388
  return isUp(cd);
×
389
}
×
390

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

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

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

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

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

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

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

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

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

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

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

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

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

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

501
  for(auto& i : items) {
×
502
    sum += i.first;
×
503
    pick.push_back({sum, i.second});
×
504
  }
×
505

506
  if (sum == 0) {
×
507
    throw std::invalid_argument("The sum of items cannot be zero");
×
508
  }
×
509

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

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

522
  vector<T> pick;
×
523
  pick.reserve(items.size());
×
524

525
  for(auto& item : items) {
×
526
    pick.push_back(item);
×
527
  }
×
528

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

531
  if (count == 0) {
×
532
    return vector<T>();
×
533
  }
×
534

535
  std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
×
536

537
  vector<T> result = {pick.begin(), pick.begin() + count};
×
538
  return result;
×
539
}
×
540

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

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

559
  double lat = 0, lon = 0;
×
560
  if(!getLatLon(ip, lat, lon))
×
561
    return false;
×
562

563
  if(lat > 0) {
×
564
    lathem='N';
×
565
  }
×
566
  else {
×
567
    lat = -lat;
×
568
    lathem='S';
×
569
  }
×
570

571
  if(lon > 0) {
×
572
    lonhem='E';
×
573
  }
×
574
  else {
×
575
    lon = -lon;
×
576
    lonhem='W';
×
577
  }
×
578

579
  latdeg = lat;
×
580
  latmin = (lat - latdeg)*60.0;
×
581
  latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
×
582

583
  londeg = lon;
×
584
  lonmin = (lon - londeg)*60.0;
×
585
  lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
×
586

587
  // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
588

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

591
  loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
×
592
  return true;
×
593
}
×
594

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

620
static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, domainid_t zoneid)
621
{
×
622
  static LockGuarded<UeberBackend> s_ub;
×
623

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

636
static bool getAuth(const ZoneName& name, uint16_t qtype, SOAData* soaData, Netmask remote)
637
{
×
638
  static LockGuarded<UeberBackend> s_ub;
×
639

640
  {
×
641
    auto ueback = s_ub.lock();
×
642
    return ueback->getAuth(name, qtype, soaData, remote);
×
643
  }
×
644
}
×
645

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

656
static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
657
{
×
658
  vector<ComboAddress> ret;
×
659

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

673
  return ret;
×
674
}
×
675

676
static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
677
{
×
678
  vector<string> result;
×
679
  result.reserve(items.size());
×
680

681
  for (const auto& item : items) {
×
682
    result.emplace_back(item.toString());
×
683
  }
×
684

685
  return result;
×
686
}
×
687

688
static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
689
{
×
690
  vector<ComboAddress> result;
×
691
  result.reserve(items.size());
×
692

693
  for(const auto& item : items) {
×
694
    result.emplace_back(ComboAddress(item.second, port));
×
695
  }
×
696

697
  return result;
×
698
}
×
699

700
/**
701
 * Reads and unify single or multiple sets of ips :
702
 * - {'192.0.2.1', '192.0.2.2'}
703
 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
704
 */
705

706
static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
707
{
×
708
  vector<vector<ComboAddress>> candidates;
×
709

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

723
static vector<string> convStringList(const iplist_t& items)
724
{
×
725
  vector<string> result;
×
726
  result.reserve(items.size());
×
727

728
  for(const auto& item : items) {
×
729
    result.emplace_back(item.second);
×
730
  }
×
731

732
  return result;
×
733
}
×
734

735
static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
736
{
×
737
  vector<pair<int,string> > result;
×
738
  result.reserve(items.size());
×
739

740
  for(const auto& item : items) {
×
741
    result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
×
742
  }
×
743

744
  return result;
×
745
}
×
746

747
bool g_LuaRecordSharedState;
748

749
typedef struct AuthLuaRecordContext
750
{
751
  ComboAddress          bestwho;
752
  DNSName               qname;
753
  DNSZoneRecord         zone_record;
754
  DNSName               zone;
755
  Netmask               remote;
756
} lua_record_ctx_t;
757

758
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
759

760
/*
761
 *  Holds computed hashes for a given entry
762
 */
763
struct EntryHashesHolder
764
{
765
  std::atomic<size_t> weight;
766
  std::string entry;
767
  SharedLockGuarded<std::vector<unsigned int>> hashes;
768
  std::atomic<time_t> lastUsed;
769

770
  EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
×
771
  }
×
772

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

792
using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
793

794
static SharedLockGuarded<std::map<
795
  zone_hashes_key_t, // zoneid qname entry
796
  std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
797
  >>
798
s_zone_hashes;
799

800
static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
801

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

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

831
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)
832
{
×
833
  std::vector<std::shared_ptr<EntryHashesHolder>> result{};
×
834
  std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
×
835

836
  {
×
837
    time_t now = time(nullptr);
×
838
    auto locked = s_zone_hashes.read_lock();
×
839

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

854
    for (auto& [key, entry]: newEntries) {
×
855
      result.push_back(entry);
×
856
      (*wlocked)[key] = std::move(entry);
×
857
    }
×
858
  }
×
859

860
  return result;
×
861
}
×
862

863
static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
864
{
×
865
  const auto& zoneId = s_lua_record_ctx->zone_record.domain_id;
×
866
  const auto queryName = s_lua_record_ctx->qname.toString();
×
867
  unsigned int sel = std::numeric_limits<unsigned int>::max();
×
868
  unsigned int min = std::numeric_limits<unsigned int>::max();
×
869

870
  boost::optional<std::string> ret;
×
871
  boost::optional<std::string> first;
×
872

873
  cleanZoneHashes();
×
874

875
  auto entries = getCHashedEntries(zoneId, queryName, items);
×
876

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

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

910
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)
911
{
×
912
  vector<vector<ComboAddress> > candidates;
×
913
  opts_t opts;
×
914
  if (options) {
×
915
    opts = *options;
×
916
  }
×
917

918
  candidates = convMultiComboAddressList(ips, port);
×
919

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

938
  // All units down or have not completed their checks yet.
939
  if (incompleteCheck) {
×
940
    throw std::runtime_error("if{url,port}up health check has not completed yet");
×
941
  }
×
942

943
  // Apply backupSelector on all candidates
944
  vector<ComboAddress> ret{};
×
945
  for(const auto& unit : candidates) {
×
946
    ret.insert(ret.end(), unit.begin(), unit.end());
×
947
  }
×
948

949
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
×
950
  return convComboAddressListToString(res);
×
951
}
×
952

953
// Lua functions available to the user
954

955
static string lua_latlon()
956
{
×
957
  double lat{0};
×
958
  double lon{0};
×
959
  getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
×
960
  return std::to_string(lat)+" "+std::to_string(lon);
×
961
}
×
962

963
static string lua_latlonloc()
964
{
×
965
  string loc;
×
966
  getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
×
967
  return loc;
×
968
}
×
969

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

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

998
static string lua_createReverse(const string &format, boost::optional<opts_t> exceptions)
999
{
×
1000
  try {
×
1001
    auto labels = s_lua_record_ctx->qname.getRawLabels();
×
1002
    if (labels.size() < 4) {
×
1003
      return {"unknown"};
×
1004
    }
×
1005

1006
    vector<ComboAddress> candidates;
×
1007

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

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

1025
    boost::format fmt2("%02x%02x%02x%02x");
×
1026
    for (int i = 3; i >= 0; --i) {
×
1027
      fmt2 % atoi(labels[i].c_str());
×
1028
    }
×
1029

1030
    fmt % (fmt2.str());
×
1031

1032
    return fmt.str();
×
1033
  }
×
1034
  catch(std::exception& ex) {
×
1035
    g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
×
1036
  }
×
1037
  return {"error"};
×
1038
}
×
1039

1040
static string lua_createForward()
1041
{
×
1042
  static string allZerosIP{"0.0.0.0"};
×
1043
  try {
×
1044
    DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
1045
    if (!record_name.isWildcard()) {
×
1046
      return allZerosIP;
×
1047
    }
×
1048
    record_name.chopOff();
×
1049
    DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
1050

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

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

1068
      // either hex string, or 12-13-14-15
1069
      vector<string> ip_parts;
×
1070

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

1105
static string lua_createForward6()
1106
{
×
1107
   static string allZerosIP{"::"};
×
1108
   try {
×
1109
     DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
1110
     if (!record_name.isWildcard()) {
×
1111
       return allZerosIP;
×
1112
     }
×
1113
     record_name.chopOff();
×
1114
     DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
1115

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

1146
        ComboAddress address(fulladdress);
×
1147
        return address.toString();
×
1148
      }
×
1149
    }
×
1150
    return allZerosIP;
×
1151
  } catch (const PDNSException &e) {
×
1152
    return allZerosIP;
×
1153
  }
×
1154
}
×
1155

1156
static string lua_createReverse6(const string &format, boost::optional<opts_t> exceptions)
1157
{
×
1158
  vector<ComboAddress> candidates;
×
1159

1160
  try {
×
1161
    auto labels= s_lua_record_ctx->qname.getRawLabels();
×
1162
    if (labels.size()<32) {
×
1163
      return {"unknown"};
×
1164
    }
×
1165

1166
    boost::format fmt(format);
×
1167
    fmt.exceptions(boost::io::all_error_bits ^ (boost::io::too_many_args_bit | boost::io::too_few_args_bit));
×
1168

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

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

1194
    string dashed=ip6.toString();
×
1195
    std::replace(dashed.begin(), dashed.end(), ':', '-');
×
1196

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

1207
    for (int byte = 31; byte >= 0; --byte) {
×
1208
      fmt % labels[byte];
×
1209
    }
×
1210
    fmt % dashed;
×
1211

1212
    for(const auto& lquad : quads) {
×
1213
      fmt % lquad;
×
1214
    }
×
1215

1216
    return fmt.str();
×
1217
  }
×
1218
  catch(std::exception& ex) {
×
1219
    g_log<<Logger::Error<<"Lua record exception: "<<ex.what()<<endl;
×
1220
  }
×
1221
  catch(PDNSException& ex) {
×
1222
    g_log<<Logger::Error<<"Lua record exception: "<<ex.reason<<endl;
×
1223
  }
×
1224
  return {"unknown"};
×
1225
}
×
1226

1227
static vector<string> lua_filterForward(const string& address, NetmaskGroup& nmg, boost::optional<string> fallback)
1228
{
×
1229
  ComboAddress caddr(address);
×
1230

1231
  if (nmg.match(ComboAddress(address))) {
×
1232
    return {address};
×
1233
  }
×
1234
  if (fallback) {
×
1235
    if (fallback->empty()) {
×
1236
      // if fallback is an empty string, return an empty array
1237
      return {};
×
1238
    }
×
1239
    return {*fallback};
×
1240
  }
×
1241

1242
  if (caddr.isIPv4()) {
×
1243
    return {string("0.0.0.0")};
×
1244
  }
×
1245
  return {"::"};
×
1246
}
×
1247

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

1261
  auto checker = [](const ComboAddress& addr, const opts_t& opts) -> int {
×
1262
    return g_up.isUp(addr, opts);
×
1263
  };
×
1264
  return genericIfUp(ips, std::move(options), checker, port);
×
1265
}
×
1266

1267
static vector<string> lua_ifurlextup(const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options)
1268
{
×
1269
  vector<ComboAddress> candidates;
×
1270
  opts_t opts;
×
1271
  if (options) {
×
1272
    opts = *options;
×
1273
  }
×
1274

1275
  ComboAddress ca_unspec;
×
1276
  ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1277

1278
  // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1279
  bool incompleteCheck{true};
×
1280
  for (const auto& [count, unitmap] : ipurls) {
×
1281
    // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1282
    vector<ComboAddress> available;
×
1283

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

1302
  // All units down or have not completed their checks yet.
1303
  if (incompleteCheck) {
×
1304
    throw std::runtime_error("ifexturlup health check has not completed yet");
×
1305
  }
×
1306

1307
  // Apply backupSelector on all candidates
1308
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
×
1309
  return convComboAddressListToString(res);
×
1310
}
×
1311

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

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

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

1343
  items.reserve(ips.capacity());
×
1344
  bool available = false;
×
1345

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

1359
  // All units down, apply backupSelector on all candidates
1360
  return pickWeightedRandom<ComboAddress>(items).toString();
×
1361
}
×
1362

1363
static vector<string> lua_pickrandomsample(int n, const iplist_t& ips)
1364
{
×
1365
  vector<string> items = convStringList(ips);
×
1366
  return pickRandomSample<string>(n, items);
×
1367
}
×
1368

1369
static string lua_pickhashed(const iplist_t& ips)
1370
{
×
1371
  vector<string> items = convStringList(ips);
×
1372
  return pickHashed<string>(s_lua_record_ctx->bestwho, items);
×
1373
}
×
1374

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

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

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

1400
  return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1401
}
×
1402

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

1412
  items.reserve(ips.size());
×
1413
  for (auto& address : ips) {
×
1414
    items.emplace_back(atoi(address.second[1].c_str()), address.second[2]);
×
1415
  }
×
1416

1417
  return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
×
1418
}
×
1419

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

1429
  items.reserve(ips.size());
×
1430
  for (const auto& entry : ips) {
×
1431
    items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
×
1432
  }
×
1433

1434
  return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1435
}
×
1436

1437
static string lua_pickclosest(const iplist_t& ips)
1438
{
×
1439
  vector<ComboAddress> conv = convComboAddressList(ips);
×
1440

1441
  return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1442
}
×
1443

1444
static void lua_report(const string& /* event */, const boost::optional<string>& /* line */)
1445
{
×
1446
  throw std::runtime_error("Script took too long");
×
1447
}
×
1448

1449
static string lua_geoiplookup(const string &address, const GeoIPInterface::GeoIPQueryAttribute attr)
1450
{
×
1451
  return getGeo(address, attr);
×
1452
}
×
1453

1454
using combovar_t = const boost::variant<string,vector<pair<int,string> > >;
1455

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

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

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

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

1489
}
×
1490

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

1501
static bool lua_region(const combovar_t& var)
1502
{
×
1503
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1504
  return doCompare(var, res, [](const std::string& arg1, const std::string& arg2) -> bool {
×
1505
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
×
1506
    });
×
1507

1508
}
×
1509

1510
static string lua_regionCode()
1511
{
×
1512
  string unknown("unknown");
×
1513
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1514
  if ( res == unknown ) {
×
1515
   return {"--"};
×
1516
  }
×
1517
  return res;
×
1518
}
×
1519

1520
static bool lua_netmask(const iplist_t& ips)
1521
{
×
1522
  for (const auto& addr : ips) {
×
1523
    Netmask netmask(addr.second);
×
1524
    if (netmask.match(s_lua_record_ctx->bestwho)) {
×
1525
      return true;
×
1526
    }
×
1527
  }
×
1528
  return false;
×
1529
}
×
1530

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

1559
static vector<string> lua_all(const vector< pair<int,string> >& ips)
1560
{
×
1561
  vector<string> result;
×
1562
  result.reserve(ips.size());
×
1563

1564
  for (const auto& address : ips) {
×
1565
      result.emplace_back(address.second);
×
1566
  }
×
1567
  if(result.empty()) {
×
1568
    throw std::invalid_argument("The IP list cannot be empty");
×
1569
  }
×
1570
  return result;
×
1571
}
×
1572

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

1587
    if (!getAuth(rec, qtype, &soaData, s_lua_record_ctx->remote)) {
×
1588
      return ret;
×
1589
    }
×
1590

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

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

1623
// Lua variables available to the user
1624

1625
static std::unordered_map<std::string, int> lua_variables{
1626
  {"ASn", GeoIPInterface::GeoIPQueryAttribute::ASn},
1627
  {"City", GeoIPInterface::GeoIPQueryAttribute::City},
1628
  {"Continent", GeoIPInterface::GeoIPQueryAttribute::Continent},
1629
  {"Country", GeoIPInterface::GeoIPQueryAttribute::Country},
1630
  {"Country2", GeoIPInterface::GeoIPQueryAttribute::Country2},
1631
  {"Name", GeoIPInterface::GeoIPQueryAttribute::Name},
1632
  {"Region", GeoIPInterface::GeoIPQueryAttribute::Region},
1633
  {"Location", GeoIPInterface::GeoIPQueryAttribute::Location}
1634
};
1635

1636
static void setupLuaRecords(LuaContext& lua)
1637
{
×
1638
  lua.writeFunction("report", [](const string& event, const boost::optional<string>& line) -> void {
×
1639
      lua_report(event, line);
×
1640
    });
×
1641

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1766
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)
1767
{
×
1768
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1769

1770
  try {
×
1771
    if(!LUA ||                  // we don't have a Lua state yet
×
1772
       !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1773
      LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
×
1774
      setupLuaRecords(*LUA->getLua());
×
1775
    }
×
1776

1777
    LuaContext& lua = *LUA->getLua();
×
1778

1779
    s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1780
    s_lua_record_ctx->qname = query;
×
1781
    s_lua_record_ctx->zone_record = zone_record;
×
1782
    s_lua_record_ctx->zone = zone;
×
1783
    s_lua_record_ctx->remote = dnsp.getRealRemote();
×
1784

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

1804
    if (g_luaRecordExecLimit > 0) {
×
1805
      lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1806
    }
×
1807

1808
    string actual;
×
1809
    if(!code.empty() && code[0]!=';')
×
1810
      actual = "return " + code;
×
1811
    else
×
1812
      actual = code.substr(1);
×
1813

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

1816
    vector<string> contents;
×
1817
    if(auto str = boost::get<string>(&content))
×
1818
      contents.push_back(*str);
×
1819
    else
×
1820
      for(const auto& c : boost::get<vector<pair<int,string>>>(content))
×
1821
        contents.push_back(c.second);
×
1822

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

1843
  return ret;
×
1844
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc