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

PowerDNS / pdns / 15254479060

26 May 2025 12:53PM UTC coverage: 63.691% (+0.1%) from 63.57%
15254479060

push

github

web-flow
Merge pull request #15512 from miodvallat/blinds

Bind-style views

42368 of 101390 branches covered (41.79%)

Branch coverage included in aggregate %.

1164 of 1378 new or added lines in 33 files covered. (84.47%)

39 existing lines in 10 files now uncovered.

130646 of 170256 relevant lines covered (76.74%)

4645349.81 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
  {
276✔
89
    d_checkerThreadStarted.clear();
276✔
90
  }
276✔
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, domainid_t 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, Netmask remote)
633
{
×
634
  static LockGuarded<UeberBackend> s_ub;
×
635

636
  {
×
637
    auto ueback = s_ub.lock();
×
NEW
638
    return ueback->getAuth(name, qtype, soaData, remote);
×
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
  Netmask               remote;
752
} lua_record_ctx_t;
753

754
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
755

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

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

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

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

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

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

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

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

827
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)
828
{
×
829
  std::vector<std::shared_ptr<EntryHashesHolder>> result{};
×
830
  std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
×
831

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

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

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

856
  return result;
×
857
}
×
858

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

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

869
  cleanZoneHashes();
×
870

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

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

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

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

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

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

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

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

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

949
// Lua functions available to the user
950

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

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

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

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

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

1002
    vector<ComboAddress> candidates;
×
1003

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1485
}
×
1486

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

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

1504
}
×
1505

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

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

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

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

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

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

NEW
1583
    if (!getAuth(rec, qtype, &soaData, s_lua_record_ctx->remote)) {
×
1584
      return ret;
×
1585
    }
×
1586

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

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

1619
// Lua variables available to the user
1620

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

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

1638
  if (g_luaRecordExecLimit > 0) {
×
1639
    lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
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
  if(!LUA ||                  // we don't have a Lua state yet
×
1769
     !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1770
    LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
×
1771
    setupLuaRecords(*LUA->getLua());
×
1772
  }
×
1773

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

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

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

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

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

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

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

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

1839
  return ret;
×
1840
}
×
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