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

PowerDNS / pdns / 19736981718

27 Nov 2025 12:55PM UTC coverage: 73.081% (+0.01%) from 73.071%
19736981718

push

github

web-flow
Merge pull request #16564 from miodvallat/stowaway

auth lua: add missing catch block for STL exceptions in createForward

38507 of 63428 branches covered (60.71%)

Branch coverage included in aggregate %.

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

54 existing lines in 11 files now uncovered.

128060 of 164492 relevant lines covered (77.85%)

5017172.29 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
  {
278✔
89
    d_checkerThreadStarted.clear();
278✔
90
  }
278✔
91
  ~IsUpOracle() = default;
×
92
  int isUp(const ComboAddress& remote, const opts_t& opts);
93
  int isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts);
94
  //NOLINTNEXTLINE(readability-identifier-length)
95
  int isUp(const CheckDesc& cd);
96

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

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

121
      MiniCurl minicurl(useragent, false);
×
122

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

655
  return ret;
×
656
}
×
657

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

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

667
  return result;
×
668
}
×
669

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

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

679
  return result;
×
680
}
×
681

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

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

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

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

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

714
  return result;
×
715
}
×
716

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

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

726
  return result;
×
727
}
×
728

729
bool g_LuaRecordSharedState;
730

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

740
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
741

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

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

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

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

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

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

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

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

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

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

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

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

842
  return result;
×
843
}
×
844

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

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

855
  cleanZoneHashes();
×
856

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

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

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

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

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

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

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

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

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

935
// Lua functions available to the user
936

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

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

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

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

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

988
    vector<ComboAddress> candidates;
×
989

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1259
  ComboAddress ca_unspec;
×
1260
  ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1261

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

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

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

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

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

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

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

1327
  items.reserve(ips.capacity());
×
1328
  bool available = false;
×
1329

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

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

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

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

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

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

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

1384
  return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1385
}
×
1386

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

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

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

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

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

1418
  return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1419
}
×
1420

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

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

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

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

1438
using combovar_t = const boost::variant<string,vector<pair<int,string> > >;
1439

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

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

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

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

1473
}
×
1474

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

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

1492
}
×
1493

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

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

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

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

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

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

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

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

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

1607
// Lua variables available to the user
1608

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1747
  lua.writeVariable("GeoIPQueryAttribute", lua_variables);
×
1748
}
×
1749

1750
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)
1751
{
×
1752
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1753

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

1761
    LuaContext& lua = *LUA->getLua();
×
1762

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

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

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

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

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

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

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

1827
  return ret;
×
1828
}
×
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