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

PowerDNS / pdns / 13672152929

05 Mar 2025 08:59AM UTC coverage: 60.501% (-4.0%) from 64.5%
13672152929

push

github

web-flow
Merge pull request #15127 from miodvallat/10431

auth lua records: new option to set the http status code to match in ifurlup function

34778 of 90438 branches covered (38.46%)

Branch coverage included in aggregate %.

0 of 9 new or added lines in 2 files covered. (0.0%)

6624 existing lines in 77 files now uncovered.

120817 of 166741 relevant lines covered (72.46%)

4337695.84 hits per line

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

0.18
/pdns/lua-record.cc
1
#include <thread>
2
#include <future>
3
#include <boost/format.hpp>
4
#include <boost/uuid/string_generator.hpp>
5
#include <utility>
6
#include <algorithm>
7
#include <random>
8
#include "qtype.hh"
9
#include <tuple>
10
#include "version.hh"
11
#include "ext/luawrapper/include/LuaContext.hpp"
12
#include "lock.hh"
13
#include "lua-auth4.hh"
14
#include "sstuff.hh"
15
#include "minicurl.hh"
16
#include "ueberbackend.hh"
17
#include "dns_random.hh"
18
#include "auth-main.hh"
19
#include "../modules/geoipbackend/geoipinterface.hh" // only for the enum
20

21
/* to do:
22
   block AXFR unless TSIG, or override
23

24
   investigate IPv6
25

26
   check the wildcard 'no cache' stuff, we may get it wrong
27

28
   ponder ECS scopemask setting
29

30
   ponder netmask tree from file for huge number of netmasks
31

32
   add attribute for certificate check in genericIfUp
33

34
   add list of current monitors
35
      expire them too?
36

37
   pool of UeberBackends?
38

39
   Pool checks ?
40
 */
41

42
extern int  g_luaRecordExecLimit;
43

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

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

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

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

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

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

NEW
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"));
×
NEW
134
        content=minicurl.getURL(cd.url, rem, &src, timeout, false, false, byteslimit, http_code);
×
135
      }
×
136
      else {
×
NEW
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
//NOLINTNEXTLINE(readability-identifier-length)
329
int IsUpOracle::isUp(const CheckDesc& cd)
330
{
×
331
  if (!d_checkerThreadStarted.test_and_set()) {
×
332
    d_checkerThread = std::make_unique<std::thread>([this] { return checkThread(); });
×
333
  }
×
334
  time_t now = time(nullptr);
×
335
  {
×
336
    auto statuses = d_statuses.read_lock();
×
337
    auto iter = statuses->find(cd);
×
338
    if (iter != statuses->end()) {
×
339
      iter->second->lastAccess = now;
×
340
      if (iter->second->weight > 0) {
×
341
        return iter->second->weight;
×
342
      }
×
343
      return static_cast<int>(iter->second->status);
×
344
    }
×
345
  }
×
346
  // try to parse options so we don't insert any malformed content
347
  if (cd.opts.count("source")) {
×
348
    ComboAddress src(cd.opts.at("source"));
×
349
  }
×
350
  {
×
351
    auto statuses = d_statuses.write_lock();
×
352
    // Make sure we don't insert new entry twice now we have the lock
353
    if (statuses->find(cd) == statuses->end()) {
×
354
      (*statuses)[cd] = std::make_unique<CheckState>(now);
×
355
    }
×
356
  }
×
357
  return 0;
×
358
}
×
359

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

654
  return ret;
×
655
}
×
656

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

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

666
  return result;
×
667
}
×
668

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

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

678
  return result;
×
679
}
×
680

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

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

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

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

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

713
  return result;
×
714
}
×
715

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

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

725
  return result;
×
726
}
×
727

728
bool g_LuaRecordSharedState;
729

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

738
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
739

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

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

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

772
using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
773

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

780
static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
781

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

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

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

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

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

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

840
  return result;
×
841
}
×
842

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

850
  boost::optional<std::string> ret;
×
851
  boost::optional<std::string> first;
×
852

853
  cleanZoneHashes();
×
854

855
  auto entries = getCHashedEntries(zoneId, queryName, items);
×
856

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

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

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

898
  candidates = convMultiComboAddressList(ips, port);
×
899

900
  for (const auto& unit : candidates) {
×
901
    vector<ComboAddress> available;
×
902
    for(const auto& address : unit) {
×
903
      if (upcheckf(address, opts)) {
×
904
        available.push_back(address);
×
905
      }
×
906
    }
×
907
    if(!available.empty()) {
×
908
      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
909
      return convComboAddressListToString(res);
×
910
    }
×
911
  }
×
912

913
  // All units down, apply backupSelector on all candidates
914
  vector<ComboAddress> ret{};
×
915
  for(const auto& unit : candidates) {
×
916
    ret.insert(ret.end(), unit.begin(), unit.end());
×
917
  }
×
918

919
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
×
920
  return convComboAddressListToString(res);
×
921
}
×
922

923
// Lua functions available to the user
924

925
static string lua_latlon()
926
{
×
927
  double lat{0};
×
928
  double lon{0};
×
929
  getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
×
930
  return std::to_string(lat)+" "+std::to_string(lon);
×
931
}
×
932

933
static string lua_latlonloc()
934
{
×
935
  string loc;
×
936
  getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
×
937
  return loc;
×
938
}
×
939

940
static string lua_closestMagic()
941
{
×
942
  vector<ComboAddress> candidates;
×
943
  // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
944
  for (auto label : s_lua_record_ctx->qname.getRawLabels()) {
×
945
    std::replace(label.begin(), label.end(), '-', '.');
×
946
    try {
×
947
      candidates.emplace_back(label);
×
948
    } catch (const PDNSException& exc) {
×
949
      // no need to continue as we most likely reached the end of the ip list
950
      break ;
×
951
    }
×
952
  }
×
953
  return pickclosest(s_lua_record_ctx->bestwho, candidates).toString();
×
954
}
×
955

956
static string lua_latlonMagic()
957
{
×
958
  auto labels = s_lua_record_ctx->qname.getRawLabels();
×
959
  if (labels.size() < 4) {
×
960
    return {"unknown"};
×
961
  }
×
962
  double lat{0};
×
963
  double lon{0};
×
964
  getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
×
965
  return std::to_string(lat)+" "+std::to_string(lon);
×
966
}
×
967

968
static string lua_createReverse(const string &format, boost::optional<opts_t> exceptions)
969
{
×
970
  try {
×
971
    auto labels = s_lua_record_ctx->qname.getRawLabels();
×
972
    if (labels.size() < 4) {
×
973
      return {"unknown"};
×
974
    }
×
975

976
    vector<ComboAddress> candidates;
×
977

978
    // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
979
    // exceptions["1.2.3.4"]="bert.powerdns.com" then provides an exception
980
    if (exceptions) {
×
981
      ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
×
982
      const auto& uom = *exceptions;
×
983
      for (const auto& address : uom) {
×
984
        if(ComboAddress(address.first, 0) == req) {
×
985
          return address.second;
×
986
        }
×
987
      }
×
988
    }
×
989
    boost::format fmt(format);
×
990
    fmt.exceptions(boost::io::all_error_bits ^ (boost::io::too_many_args_bit | boost::io::too_few_args_bit));
×
991
    fmt % labels[3] % labels[2] % labels[1] % labels[0];
×
992

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

995
    boost::format fmt2("%02x%02x%02x%02x");
×
996
    for (int i = 3; i >= 0; --i) {
×
997
      fmt2 % atoi(labels[i].c_str());
×
998
    }
×
999

1000
    fmt % (fmt2.str());
×
1001

1002
    return fmt.str();
×
1003
  }
×
1004
  catch(std::exception& ex) {
×
1005
    g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
×
1006
  }
×
1007
  return {"error"};
×
1008
}
×
1009

1010
static string lua_createForward()
1011
{
×
1012
  static string allZerosIP{"0.0.0.0"};
×
1013
  try {
×
1014
    DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
1015
    if (!record_name.isWildcard()) {
×
1016
      return allZerosIP;
×
1017
    }
×
1018
    record_name.chopOff();
×
1019
    DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
1020

1021
    // parts is something like ["1", "2", "3", "4", "static"] or
1022
    // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
1023
    auto parts = rel.getRawLabels();
×
1024
    // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
1025
    if (parts.size() >= 4) {
×
1026
      ComboAddress address(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
×
1027
      return address.toString();
×
1028
    }
×
1029
    if (!parts.empty()) {
×
1030
      auto& input = parts.at(0);
×
1031

1032
      // allow a word without - in front, as long as it does not contain anything that could be a number
1033
      size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
×
1034
      if (nonhexprefix > 0) {
×
1035
        input = input.substr(nonhexprefix);
×
1036
      }
×
1037

1038
      // either hex string, or 12-13-14-15
1039
      vector<string> ip_parts;
×
1040

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

1075
static string lua_createForward6()
1076
{
×
1077
   static string allZerosIP{"::"};
×
1078
   try {
×
1079
     DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
1080
     if (!record_name.isWildcard()) {
×
1081
       return allZerosIP;
×
1082
     }
×
1083
     record_name.chopOff();
×
1084
     DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
1085

1086
     auto parts = rel.getRawLabels();
×
1087
     if (parts.size() == 8) {
×
1088
       string tot;
×
1089
       for (int chunk = 0; chunk < 8; ++chunk) {
×
1090
         if (chunk != 0) {
×
1091
           tot.append(1, ':');
×
1092
         }
×
1093
        tot += parts.at(chunk);
×
1094
      }
×
1095
      ComboAddress address(tot);
×
1096
      return address.toString();
×
1097
    }
×
1098
    if (parts.size() == 1) {
×
1099
      if (parts[0].find('-') != std::string::npos) {
×
1100
        std::replace(parts[0].begin(), parts[0].end(), '-', ':');
×
1101
        ComboAddress address(parts[0]);
×
1102
        return address.toString();
×
1103
      }
×
1104
      if (parts[0].size() >= 32) {
×
1105
        auto ippart = parts[0].substr(parts[0].size()-32);
×
1106
        auto fulladdress =
×
1107
          ippart.substr(0, 4) + ":" +
×
1108
          ippart.substr(4, 4) + ":" +
×
1109
          ippart.substr(8, 4) + ":" +
×
1110
          ippart.substr(12, 4) + ":" +
×
1111
          ippart.substr(16, 4) + ":" +
×
1112
          ippart.substr(20, 4) + ":" +
×
1113
          ippart.substr(24, 4) + ":" +
×
1114
          ippart.substr(28, 4);
×
1115

1116
        ComboAddress address(fulladdress);
×
1117
        return address.toString();
×
1118
      }
×
1119
    }
×
1120
    return allZerosIP;
×
1121
  } catch (const PDNSException &e) {
×
1122
    return allZerosIP;
×
1123
  }
×
1124
}
×
1125

1126
static string lua_createReverse6(const string &format, boost::optional<opts_t> exceptions)
1127
{
×
1128
  vector<ComboAddress> candidates;
×
1129

1130
  try {
×
1131
    auto labels= s_lua_record_ctx->qname.getRawLabels();
×
1132
    if (labels.size()<32) {
×
1133
      return {"unknown"};
×
1134
    }
×
1135

1136
    boost::format fmt(format);
×
1137
    fmt.exceptions(boost::io::all_error_bits ^ (boost::io::too_many_args_bit | boost::io::too_few_args_bit));
×
1138

1139
    string together;
×
1140
    vector<string> quads;
×
1141
    for (int chunk = 0; chunk < 8; ++chunk) {
×
1142
      if (chunk != 0) {
×
1143
        together += ":";
×
1144
      }
×
1145
      string lquad;
×
1146
      for (int quartet = 0; quartet < 4; ++quartet) {
×
1147
        lquad.append(1, labels[31 - chunk * 4 - quartet][0]);
×
1148
        together += labels[31 - chunk * 4 - quartet][0];
×
1149
      }
×
1150
      quads.push_back(lquad);
×
1151
    }
×
1152
    ComboAddress ip6(together,0);
×
1153

1154
    if (exceptions) {
×
1155
      auto& addrs=*exceptions;
×
1156
      for(const auto& addr: addrs) {
×
1157
        // this makes sure we catch all forms of the address
1158
        if (ComboAddress(addr.first, 0) == ip6) {
×
1159
          return addr.second;
×
1160
        }
×
1161
      }
×
1162
    }
×
1163

1164
    string dashed=ip6.toString();
×
1165
    std::replace(dashed.begin(), dashed.end(), ':', '-');
×
1166

1167
    // https://github.com/PowerDNS/pdns/issues/7524
1168
    if (boost::ends_with(dashed, "-")) {
×
1169
      // "a--a-" -> "a--a-0"
1170
      dashed.push_back('0');
×
1171
    }
×
1172
    if (boost::starts_with(dashed, "-") || dashed.compare(2, 2, "--") == 0) {
×
1173
      // "-a--a" -> "0-a--a"               "aa--a" -> "0aa--a"
1174
      dashed.insert(0, "0");
×
1175
    }
×
1176

1177
    for (int byte = 31; byte >= 0; --byte) {
×
1178
      fmt % labels[byte];
×
1179
    }
×
1180
    fmt % dashed;
×
1181

1182
    for(const auto& lquad : quads) {
×
1183
      fmt % lquad;
×
1184
    }
×
1185

1186
    return fmt.str();
×
1187
  }
×
1188
  catch(std::exception& ex) {
×
1189
    g_log<<Logger::Error<<"Lua record exception: "<<ex.what()<<endl;
×
1190
  }
×
1191
  catch(PDNSException& ex) {
×
1192
    g_log<<Logger::Error<<"Lua record exception: "<<ex.reason<<endl;
×
1193
  }
×
1194
  return {"unknown"};
×
1195
}
×
1196

1197
static vector<string> lua_filterForward(const string& address, NetmaskGroup& nmg, boost::optional<string> fallback)
1198
{
×
1199
  ComboAddress caddr(address);
×
1200

1201
  if (nmg.match(ComboAddress(address))) {
×
1202
    return {address};
×
1203
  }
×
1204
  if (fallback) {
×
1205
    if (fallback->empty()) {
×
1206
      // if fallback is an empty string, return an empty array
1207
      return {};
×
1208
    }
×
1209
    return {*fallback};
×
1210
  }
×
1211

1212
  if (caddr.isIPv4()) {
×
1213
    return {string("0.0.0.0")};
×
1214
  }
×
1215
  return {"::"};
×
1216
}
×
1217

1218
/*
1219
 * Simplistic test to see if an IP address listens on a certain port
1220
 * Will return a single IP address from the set of available IP addresses. If
1221
 * no IP address is available, will return a random element of the set of
1222
 * addresses supplied for testing.
1223
 *
1224
 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
1225
 */
1226
static vector<string> lua_ifportup(int port, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options)
1227
{
×
1228
  port = std::max(port, 0);
×
1229
  port = std::min(port, static_cast<int>(std::numeric_limits<uint16_t>::max()));
×
1230

1231
  auto checker = [](const ComboAddress& addr, const opts_t& opts) -> bool {
×
1232
    return g_up.isUp(addr, opts) != 0;
×
1233
  };
×
1234
  return genericIfUp(ips, std::move(options), checker, port);
×
1235
}
×
1236

1237
static vector<string> lua_ifurlextup(const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options)
1238
{
×
1239
  vector<ComboAddress> candidates;
×
1240
  opts_t opts;
×
1241
  if (options) {
×
1242
    opts = *options;
×
1243
  }
×
1244

1245
  ComboAddress ca_unspec;
×
1246
  ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1247

1248
  // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1249
  for (const auto& [count, unitmap] : ipurls) {
×
1250
    // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1251
    vector<ComboAddress> available;
×
1252

1253
    for (const auto& [ipStr, url] : unitmap) {
×
1254
      // unit: ["192.0.2.1"] = "https://example.com"
1255
      ComboAddress address(ipStr);
×
1256
      candidates.push_back(address);
×
1257
      if (g_up.isUp(ca_unspec, url, opts) != 0) {
×
1258
        available.push_back(address);
×
1259
      }
×
1260
    }
×
1261
    if(!available.empty()) {
×
1262
      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
1263
      return convComboAddressListToString(res);
×
1264
    }
×
1265
  }
×
1266

1267
  // All units down, apply backupSelector on all candidates
1268
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
×
1269
  return convComboAddressListToString(res);
×
1270
}
×
1271

1272
static vector<string> lua_ifurlup(const std::string& url, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options)
1273
{
×
1274
  auto checker = [&url](const ComboAddress& addr, const opts_t& opts) -> bool {
×
1275
    return g_up.isUp(addr, url, opts) != 0;
×
1276
  };
×
1277
  return genericIfUp(ips, std::move(options), checker);
×
1278
}
×
1279

1280
/*
1281
 * Returns a random IP address from the supplied list
1282
 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
1283
 */
1284
static string lua_pickrandom(const iplist_t& ips)
1285
{
×
1286
  vector<string> items = convStringList(ips);
×
1287
  return pickRandom<string>(items);
×
1288
}
×
1289

1290
/*
1291
 * Based on the hash of `bestwho`, returns an IP address from the list
1292
 * supplied, weighted according to the results of isUp calls.
1293
 * @example pickselfweighted('http://example.com/weight', { "192.0.2.20", "203.0.113.4", "203.0.113.2" })
1294
 */
1295
static string lua_pickselfweighted(const std::string& url, const iplist_t& ips, boost::optional<opts_t> options)
1296
{
×
1297
  vector< pair<int, ComboAddress> > items;
×
1298
  opts_t opts;
×
1299
  if(options) {
×
1300
    opts = *options;
×
1301
  }
×
1302

1303
  items.reserve(ips.capacity());
×
1304
  bool available = false;
×
1305

1306
  vector<ComboAddress> conv = convComboAddressList(ips);
×
1307
  for (auto& entry : conv) {
×
1308
    int weight = 0;
×
1309
    weight = g_up.isUp(entry, url, opts);
×
1310
    if(weight>0) {
×
1311
      available = true;
×
1312
    }
×
1313
    items.emplace_back(weight, entry);
×
1314
  }
×
1315
  if(available) {
×
1316
    return pickWeightedHashed<ComboAddress>(s_lua_record_ctx->bestwho, items).toString();
×
1317
  }
×
1318

1319
  // All units down, apply backupSelector on all candidates
1320
  return pickWeightedRandom<ComboAddress>(items).toString();
×
1321
}
×
1322

1323
static vector<string> lua_pickrandomsample(int n, const iplist_t& ips)
1324
{
×
1325
  vector<string> items = convStringList(ips);
×
1326
  return pickRandomSample<string>(n, items);
×
1327
}
×
1328

1329
static string lua_pickhashed(const iplist_t& ips)
1330
{
×
1331
  vector<string> items = convStringList(ips);
×
1332
  return pickHashed<string>(s_lua_record_ctx->bestwho, items);
×
1333
}
×
1334

1335
/*
1336
 * Returns a random IP address from the supplied list, as weighted by the
1337
 * various ``weight`` parameters
1338
 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1339
 */
1340
static string lua_pickwrandom(const std::unordered_map<int, wiplist_t>& ips)
1341
{
×
1342
  vector< pair<int, string> > items = convIntStringPairList(ips);
×
1343
  return pickWeightedRandom<string>(items);
×
1344
}
×
1345

1346
/*
1347
 * Based on the hash of `bestwho`, returns an IP address from the list
1348
 * supplied, as weighted by the various `weight` parameters
1349
 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1350
 */
1351
static string lua_pickwhashed(std::unordered_map<int, wiplist_t> ips)
1352
{
×
1353
  vector< pair<int, string> > items;
×
1354

1355
  items.reserve(ips.size());
×
1356
  for (auto& entry : ips) {
×
1357
    items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
×
1358
  }
×
1359

1360
  return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1361
}
×
1362

1363
/*
1364
 * Based on the hash of the record name, return an IP address from the list
1365
 * supplied, as weighted by the various `weight` parameters
1366
 * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1367
 */
1368
static string lua_picknamehashed(std::unordered_map<int, wiplist_t> ips)
1369
{
×
1370
  vector< pair<int, string> > items;
×
1371

1372
  items.reserve(ips.size());
×
1373
  for (auto& address : ips) {
×
1374
    items.emplace_back(atoi(address.second[1].c_str()), address.second[2]);
×
1375
  }
×
1376

1377
  return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
×
1378
}
×
1379

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

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

1394
  return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1395
}
×
1396

1397
static string lua_pickclosest(const iplist_t& ips)
1398
{
×
1399
  vector<ComboAddress> conv = convComboAddressList(ips);
×
1400

1401
  return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1402
}
×
1403

1404
static void lua_report(const string& /* event */, const boost::optional<string>& /* line */)
1405
{
×
1406
  throw std::runtime_error("Script took too long");
×
1407
}
×
1408

1409
static string lua_geoiplookup(const string &address, const GeoIPInterface::GeoIPQueryAttribute attr)
1410
{
×
1411
  return getGeo(address, attr);
×
1412
}
×
1413

1414
using combovar_t = const boost::variant<string,vector<pair<int,string> > >;
1415

1416
static bool lua_asnum(const combovar_t& asns)
1417
{
×
1418
  string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
×
1419
  return doCompare(asns, res, [](const std::string& arg1, const std::string& arg2) -> bool {
×
1420
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
×
1421
    });
×
1422
}
×
1423

1424
static bool lua_continent(const combovar_t& continent)
1425
{
×
1426
  string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1427
  return doCompare(continent, res, [](const std::string& arg1, const std::string& arg2) -> bool {
×
1428
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
×
1429
    });
×
1430
}
×
1431

1432
static string lua_continentCode()
1433
{
×
1434
  string unknown("unknown");
×
1435
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1436
  if ( res == unknown ) {
×
1437
   return {"--"};
×
1438
  }
×
1439
  return res;
×
1440
}
×
1441

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

1449
}
×
1450

1451
static string lua_countryCode()
1452
{
×
1453
  string unknown("unknown");
×
1454
  string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1455
  if (res == unknown) {
×
1456
   return {"--"};
×
1457
  }
×
1458
  return res;
×
1459
}
×
1460

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

1468
}
×
1469

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

1480
static bool lua_netmask(const iplist_t& ips)
1481
{
×
1482
  for (const auto& addr : ips) {
×
1483
    Netmask netmask(addr.second);
×
1484
    if (netmask.match(s_lua_record_ctx->bestwho)) {
×
1485
      return true;
×
1486
    }
×
1487
  }
×
1488
  return false;
×
1489
}
×
1490

1491
/* {
1492
     {
1493
      {'192.168.0.0/16', '10.0.0.0/8'},
1494
      {'192.168.20.20', '192.168.20.21'}
1495
     },
1496
     {
1497
      {'0.0.0.0/0'}, {'192.0.2.1'}
1498
     }
1499
   }
1500
*/
1501
static string lua_view(const vector<pair<int, vector<pair<int, iplist_t> > > >& pairs)
1502
{
×
1503
  for(const auto& rule : pairs) {
×
1504
    const auto& netmasks=rule.second[0].second;
×
1505
    const auto& destinations=rule.second[1].second;
×
1506
    for(const auto& nmpair : netmasks) {
×
1507
      Netmask netmask(nmpair.second);
×
1508
      if (netmask.match(s_lua_record_ctx->bestwho)) {
×
1509
        if (destinations.empty()) {
×
1510
          throw std::invalid_argument("The IP list cannot be empty (for netmask " + netmask.toString() + ")");
×
1511
        }
×
1512
        return destinations[dns_random(destinations.size())].second;
×
1513
      }
×
1514
    }
×
1515
  }
×
1516
  return {};
×
1517
}
×
1518

1519
static vector<string> lua_all(const vector< pair<int,string> >& ips)
1520
{
×
1521
  vector<string> result;
×
1522
  result.reserve(ips.size());
×
1523

1524
  for (const auto& address : ips) {
×
1525
      result.emplace_back(address.second);
×
1526
  }
×
1527
  if(result.empty()) {
×
1528
    throw std::invalid_argument("The IP list cannot be empty");
×
1529
  }
×
1530
  return result;
×
1531
}
×
1532

1533
static vector<string> lua_dblookup(const string& record, uint16_t qtype)
1534
{
×
1535
  DNSName rec;
×
1536
  vector<string> ret;
×
1537
  try {
×
1538
    rec = DNSName(record);
×
1539
  }
×
1540
  catch (const std::exception& e) {
×
1541
    g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
×
1542
    return ret;
×
1543
  }
×
1544
  try {
×
1545
    SOAData soaData;
×
1546

1547
    if (!getAuth(rec, qtype, &soaData)) {
×
1548
      return ret;
×
1549
    }
×
1550

1551
    vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id);
×
1552
    for (const auto& drec : drs) {
×
1553
      ret.push_back(drec.dr.getContent()->getZoneRepresentation());
×
1554
    }
×
1555
  }
×
1556
  catch (std::exception& e) {
×
1557
    g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
×
1558
  }
×
1559
  return ret;
×
1560
}
×
1561

1562
static void lua_include(LuaContext& lua, const string& record)
1563
{
×
1564
  DNSName rec;
×
1565
  try {
×
1566
    rec = DNSName(record) + s_lua_record_ctx->zone;
×
1567
  } catch (const std::exception &e){
×
1568
    g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl;
×
1569
    return;
×
1570
  }
×
1571
  try {
×
1572
    vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zone_record.domain_id);
×
1573
    for(const auto& zonerecord : drs) {
×
1574
      auto luarecord = getRR<LUARecordContent>(zonerecord.dr);
×
1575
      lua.executeCode(luarecord->getCode());
×
1576
    }
×
1577
  }
×
1578
  catch(std::exception& e) {
×
1579
    g_log<<Logger::Error<<"Failed to load include record for Lua record "<<rec<<": "<<e.what()<<endl;
×
1580
  }
×
1581
}
×
1582

1583
// Lua variables available to the user
1584

1585
static std::unordered_map<std::string, int> lua_variables{
1586
  {"ASn", GeoIPInterface::GeoIPQueryAttribute::ASn},
1587
  {"City", GeoIPInterface::GeoIPQueryAttribute::City},
1588
  {"Continent", GeoIPInterface::GeoIPQueryAttribute::Continent},
1589
  {"Country", GeoIPInterface::GeoIPQueryAttribute::Country},
1590
  {"Country2", GeoIPInterface::GeoIPQueryAttribute::Country2},
1591
  {"Name", GeoIPInterface::GeoIPQueryAttribute::Name},
1592
  {"Region", GeoIPInterface::GeoIPQueryAttribute::Region},
1593
  {"Location", GeoIPInterface::GeoIPQueryAttribute::Location}
1594
};
1595

1596
static void setupLuaRecords(LuaContext& lua)
1597
{
×
1598
  lua.writeFunction("report", [](const string& event, const boost::optional<string>& line) -> void {
×
1599
      lua_report(event, line);
×
1600
    });
×
1601

1602
  if (g_luaRecordExecLimit > 0) {
×
1603
    lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1604
  }
×
1605

1606
  lua.writeFunction("latlon", []() -> string {
×
1607
      return lua_latlon();
×
1608
    });
×
1609
  lua.writeFunction("latlonloc", []() -> string {
×
1610
      return lua_latlonloc();
×
1611
    });
×
1612
  lua.writeFunction("closestMagic", []() -> string {
×
1613
      return lua_closestMagic();
×
1614
    });
×
1615
  lua.writeFunction("latlonMagic", []()-> string {
×
1616
      return lua_latlonMagic();
×
1617
    });
×
1618

1619
  lua.writeFunction("createForward", []() -> string {
×
1620
      return lua_createForward();
×
1621
    });
×
1622
  lua.writeFunction("createForward6", []() -> string {
×
1623
      return lua_createForward6();
×
1624
    });
×
1625

1626
  lua.writeFunction("createReverse", [](const string &format, boost::optional<opts_t> exceptions) -> string {
×
1627
      return lua_createReverse(format, std::move(exceptions));
×
1628
    });
×
1629
  lua.writeFunction("createReverse6", [](const string &format, boost::optional<opts_t> exceptions) -> string {
×
1630
      return lua_createReverse6(format, std::move(exceptions));
×
1631
    });
×
1632

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

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

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

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

1649
  lua.writeFunction("pickrandom", [](const iplist_t& ips) -> string {
×
1650
      return lua_pickrandom(ips);
×
1651
    });
×
1652

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

1657
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) -> vector<string> {
×
1658
      return lua_pickrandomsample(n, ips);
×
1659
    });
×
1660

1661
  lua.writeFunction("pickhashed", [](const iplist_t& ips) -> string {
×
1662
      return lua_pickhashed(ips);
×
1663
    });
×
1664
  lua.writeFunction("pickwrandom", [](const std::unordered_map<int, wiplist_t>& ips) -> string {
×
1665
      return lua_pickwrandom(ips);
×
1666
    });
×
1667

1668
  lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t> ips) -> string {
×
1669
      return lua_pickwhashed(std::move(ips));
×
1670
    });
×
1671

1672
  lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t> ips) -> string {
×
1673
      return lua_picknamehashed(std::move(ips));
×
1674
    });
×
1675
  lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) -> string {
×
1676
      return lua_pickchashed(ips);
×
1677
    });
×
1678

1679
  lua.writeFunction("pickclosest", [](const iplist_t& ips) -> string {
×
1680
      return lua_pickclosest(ips);
×
1681
    });
×
1682

1683
  lua.writeFunction("geoiplookup", [](const string &address, const GeoIPInterface::GeoIPQueryAttribute attr) -> string {
×
1684
      return lua_geoiplookup(address, attr);
×
1685
    });
×
1686

1687
  lua.writeFunction("asnum", [](const combovar_t& asns) -> bool {
×
1688
      return lua_asnum(asns);
×
1689
    });
×
1690
  lua.writeFunction("continent", [](const combovar_t& continent) -> bool {
×
1691
      return lua_continent(continent);
×
1692
    });
×
1693
  lua.writeFunction("continentCode", []() -> string {
×
1694
      return lua_continentCode();
×
1695
    });
×
1696
  lua.writeFunction("country", [](const combovar_t& var) -> bool {
×
1697
      return lua_country(var);
×
1698
    });
×
1699
  lua.writeFunction("countryCode", []() -> string {
×
1700
      return lua_countryCode();
×
1701
    });
×
1702
  lua.writeFunction("region", [](const combovar_t& var) -> bool {
×
1703
      return lua_region(var);
×
1704
    });
×
1705
  lua.writeFunction("regionCode", []() -> string {
×
1706
      return lua_regionCode();
×
1707
    });
×
1708
  lua.writeFunction("netmask", [](const iplist_t& ips) -> bool {
×
1709
      return lua_netmask(ips);
×
1710
    });
×
1711
  lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& pairs) -> string {
×
1712
      return lua_view(pairs);
×
1713
    });
×
1714

1715
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) -> vector<string> {
×
1716
      return lua_all(ips);
×
1717
    });
×
1718

1719
  lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) -> vector<string> {
×
1720
      return lua_dblookup(record, qtype);
×
1721
    });
×
1722

1723
  lua.writeFunction("include", [&lua](const string& record) -> void {
×
1724
      lua_include(lua, record);
×
1725
    });
×
1726

1727
  lua.writeVariable("GeoIPQueryAttribute", lua_variables);
×
1728
}
×
1729

1730
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)
1731
{
×
1732
  if(!LUA ||                  // we don't have a Lua state yet
×
1733
     !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1734
    LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
×
1735
    setupLuaRecords(*LUA->getLua());
×
1736
  }
×
1737

1738
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1739

1740
  LuaContext& lua = *LUA->getLua();
×
1741

1742
  s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1743
  s_lua_record_ctx->qname = query;
×
1744
  s_lua_record_ctx->zone_record = zone_record;
×
1745
  s_lua_record_ctx->zone = zone;
×
1746

1747
  lua.writeVariable("qname", query);
×
1748
  lua.writeVariable("zone", zone);
×
1749
  lua.writeVariable("zoneid", zone_record.domain_id);
×
1750
  lua.writeVariable("who", dnsp.getInnerRemote());
×
1751
  lua.writeVariable("localwho", dnsp.getLocal());
×
1752
  lua.writeVariable("dh", (dnsheader*)&dnsp.d);
×
1753
  lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
×
1754
  lua.writeVariable("tcp", dnsp.d_tcp);
×
1755
  lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
×
1756
  if(dnsp.hasEDNSSubnet()) {
×
1757
    lua.writeVariable("ecswho", dnsp.getRealRemote());
×
1758
    s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
×
1759
  }
×
1760
  else {
×
1761
    lua.writeVariable("ecswho", nullptr);
×
1762
    s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
×
1763
  }
×
1764
  lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
×
1765

1766
  try {
×
1767
    string actual;
×
1768
    if(!code.empty() && code[0]!=';')
×
1769
      actual = "return " + code;
×
1770
    else
×
1771
      actual = code.substr(1);
×
1772

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

1775
    vector<string> contents;
×
1776
    if(auto str = boost::get<string>(&content))
×
1777
      contents.push_back(*str);
×
1778
    else
×
1779
      for(const auto& c : boost::get<vector<pair<int,string>>>(content))
×
1780
        contents.push_back(c.second);
×
1781

1782
    for(const auto& content_it: contents) {
×
1783
      if(qtype==QType::TXT)
×
1784
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
×
1785
      else
×
1786
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
×
1787
    }
×
1788
  } catch(std::exception &e) {
×
1789
    g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
×
1790
    try {
×
1791
      std::rethrow_if_nested(e);
×
1792
      g_log<<endl;
×
1793
    } catch(const std::exception& ne) {
×
1794
      g_log << ": " << ne.what() << std::endl;
×
1795
    }
×
1796
    catch(const PDNSException& ne) {
×
1797
      g_log << ": " << ne.reason << std::endl;
×
1798
    }
×
1799
    throw ;
×
1800
  }
×
1801

1802
  return ret;
×
1803
}
×
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