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

PowerDNS / pdns / 12513391980

27 Dec 2024 07:42AM UTC coverage: 64.807% (+0.001%) from 64.806%
12513391980

Pull #14996

github

web-flow
Merge 1ceeffd0a into 788f396a7
Pull Request #14996: Lua up checks: finer control

37705 of 88998 branches covered (42.37%)

Branch coverage included in aggregate %.

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

28 existing lines in 11 files now uncovered.

126120 of 163793 relevant lines covered (77.0%)

4970500.95 hits per line

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

0.2
/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
    /* first check? */
75
    std::atomic<bool> first{true};
76
    /* number of successive checks returning failure */
77
    std::atomic<unsigned int> failures{0};
78
    /* last time the status was accessed */
79
    std::atomic<time_t> lastAccess{0};
80
    /* last time the status was modified */
81
    std::atomic<time_t> lastStatusUpdate{0};
82
  };
83

84
public:
85
  IsUpOracle()
86
  {
256✔
87
    d_checkerThreadStarted.clear();
256✔
88
  }
256✔
89
  ~IsUpOracle() = default;
×
90
  bool isUp(const ComboAddress& remote, const opts_t& opts);
91
  bool isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts);
92
  bool isUp(const CheckDesc& cd);
93

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

99
    string remstring;
×
100
    try {
×
101
      int timeout = 2;
×
102
      if (cd.opts.count("timeout")) {
×
103
        timeout = std::atoi(cd.opts.at("timeout").c_str());
×
104
      }
×
105
      string useragent = productName();
×
106
      if (cd.opts.count("useragent")) {
×
107
        useragent = cd.opts.at("useragent");
×
108
      }
×
109
      size_t byteslimit = 0;
×
110
      if (cd.opts.count("byteslimit")) {
×
111
        byteslimit = static_cast<size_t>(std::atoi(cd.opts.at("byteslimit").c_str()));
×
112
      }
×
113
      MiniCurl mc(useragent);
×
114

115
      string content;
×
116
      const ComboAddress* rem = nullptr;
×
117
      if(cd.rem.sin4.sin_family != AF_UNSPEC) {
×
118
        rem = &cd.rem;
×
119
        remstring = rem->toString();
×
120
      } else {
×
121
        remstring = "[externally checked IP]";
×
122
      }
×
123

124
      if (cd.opts.count("source")) {
×
125
        ComboAddress src(cd.opts.at("source"));
×
126
        content=mc.getURL(cd.url, rem, &src, timeout, false, false, byteslimit);
×
127
      }
×
128
      else {
×
129
        content=mc.getURL(cd.url, rem, nullptr, timeout, false, false, byteslimit);
×
130
      }
×
131
      if (cd.opts.count("stringmatch") && content.find(cd.opts.at("stringmatch")) == string::npos) {
×
132
        throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % cd.opts.at("stringmatch")));
×
133
      }
×
134

135
      if(!status) {
×
136
        g_log<<Logger::Info<<"LUA record monitoring declaring "<<remstring<<" UP for URL "<<cd.url<<"!"<<endl;
×
137
      }
×
138
      setUp(cd);
×
139
    }
×
140
    catch(std::exception& ne) {
×
141
      if(status || first)
×
142
        g_log<<Logger::Info<<"LUA record monitoring declaring "<<remstring<<" DOWN for URL "<<cd.url<<", error: "<<ne.what()<<endl;
×
143
      setDown(cd);
×
144
    }
×
145
  }
×
NEW
146
  void checkTCP(const CheckDesc& cd, const bool status, const bool first) { // NOLINT(readability-identifier-length)
×
147
    setThreadName("pdns/lua-c-tcp");
×
148
    try {
×
149
      int timeout = 2;
×
150
      if (cd.opts.count("timeout")) {
×
151
        timeout = std::atoi(cd.opts.at("timeout").c_str());
×
152
      }
×
153
      Socket s(cd.rem.sin4.sin_family, SOCK_STREAM);
×
154
      ComboAddress src;
×
155
      s.setNonBlocking();
×
156
      if (cd.opts.count("source")) {
×
157
        src = ComboAddress(cd.opts.at("source"));
×
158
        s.bind(src);
×
159
      }
×
160
      s.connect(cd.rem, timeout);
×
161
      if (!status) {
×
162
        g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" ";
×
163
        if(cd.opts.count("source"))
×
164
          g_log<<"(source "<<src.toString()<<") ";
×
165
        g_log<<"UP!"<<endl;
×
166
      }
×
167
      setUp(cd);
×
168
    }
×
169
    catch (const NetworkError& ne) {
×
170
      if(status || first) {
×
171
        g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" DOWN: "<<ne.what()<<endl;
×
172
      }
×
173
      setDown(cd);
×
174
    }
×
175
  }
×
176
  void checkThread()
177
  {
×
178
    setThreadName("pdns/luaupcheck");
×
179
    while (true)
×
180
    {
×
181
      std::chrono::system_clock::time_point checkStart = std::chrono::system_clock::now();
×
182
      std::vector<std::future<void>> results;
×
183
      std::vector<CheckDesc> toDelete;
×
NEW
184
      time_t interval{g_luaHealthChecksInterval};
×
UNCOV
185
      {
×
186
        // make sure there's no insertion
187
        auto statuses = d_statuses.read_lock();
×
188
        for (auto& it: *statuses) {
×
189
          auto& desc = it.first;
×
190
          auto& state = it.second;
×
NEW
191
          time_t checkInterval{0};
×
NEW
192
          auto lastAccess = std::chrono::system_clock::from_time_t(state->lastAccess);
×
193

NEW
194
          if (desc.opts.count("interval") != 0) {
×
NEW
195
            checkInterval = std::atoi(desc.opts.at("interval").c_str());
×
NEW
196
            if (checkInterval != 0) {
×
NEW
197
              interval = std::gcd(interval, checkInterval);
×
NEW
198
            }
×
NEW
199
          }
×
200

NEW
201
          if (not state->first) {
×
NEW
202
            time_t nextCheckSecond = state->lastStatusUpdate;
×
NEW
203
            if (checkInterval != 0) {
×
NEW
204
               nextCheckSecond += checkInterval;
×
NEW
205
            }
×
NEW
206
            else {
×
NEW
207
               nextCheckSecond += g_luaHealthChecksInterval;
×
NEW
208
            }
×
NEW
209
            if (checkStart < std::chrono::system_clock::from_time_t(nextCheckSecond)) {
×
NEW
210
              continue; // too early
×
NEW
211
            }
×
NEW
212
          }
×
213

214
          if (desc.url.empty()) { // TCP
×
215
            results.push_back(std::async(std::launch::async, &IsUpOracle::checkTCP, this, desc, state->status.load(), state->first.load()));
×
216
          } else { // URL
×
217
            results.push_back(std::async(std::launch::async, &IsUpOracle::checkURL, this, desc, state->status.load(), state->first.load()));
×
218
          }
×
219
          // Give it a chance to run at least once.
220
          // If minimumFailures * interval > lua-health-checks-expire-delay, then a down status will never get reported.
221
          // This is unlikely to be a problem in practice due to the default value of the expire delay being one hour.
NEW
222
          if (not state->first &&
×
NEW
223
              lastAccess < (checkStart - std::chrono::seconds(g_luaHealthChecksExpireDelay))) {
×
224
            toDelete.push_back(desc);
×
225
          }
×
226
        }
×
227
      }
×
228
      // we can release the lock as nothing will be deleted
229
      for (auto& future: results) {
×
230
        future.wait();
×
231
      }
×
232
      if (!toDelete.empty()) {
×
233
        auto statuses = d_statuses.write_lock();
×
234
        for (auto& it: toDelete) {
×
235
          statuses->erase(it);
×
236
        }
×
237
      }
×
238

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

NEW
242
      std::this_thread::sleep_until(checkStart + std::chrono::seconds(interval));
×
243
    }
×
244
  }
×
245

246
  typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
247
  SharedLockGuarded<statuses_t> d_statuses;
248

249
  std::unique_ptr<std::thread> d_checkerThread;
250
  std::atomic_flag d_checkerThreadStarted;
251

252
  void setStatus(const CheckDesc& cd, bool status)
253
  {
×
254
    auto statuses = d_statuses.write_lock();
×
255
    auto& state = (*statuses)[cd];
×
NEW
256
    state->lastStatusUpdate = time(nullptr);
×
NEW
257
    state->first = false;
×
NEW
258
    if (status) {
×
NEW
259
      state->failures = 0;
×
NEW
260
      state->status = true;
×
NEW
261
    } else {
×
NEW
262
      unsigned int minimumFailures = 1;
×
NEW
263
      if (cd.opts.count("minimumFailures") != 0) {
×
NEW
264
        unsigned int value = std::atoi(cd.opts.at("minimumFailures").c_str());
×
NEW
265
        if (value != 0) {
×
NEW
266
          minimumFailures = std::max(minimumFailures, value);
×
NEW
267
        }
×
NEW
268
      }
×
269
      // Since `status' was set to false at constructor time, we need to
270
      // recompute its value unconditionally to expose "down, but not enough
271
      // times yet" targets as up.
NEW
272
      state->status = ++state->failures < minimumFailures;
×
273
    }
×
274
  }
×
275

276
  void setDown(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
277
  {
×
278
    CheckDesc cd{rem, url, opts};
×
279
    setStatus(cd, false);
×
280
  }
×
281

282
  void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
283
  {
×
284
    CheckDesc cd{rem, url, opts};
×
285

×
286
    setStatus(cd, true);
×
287
  }
×
288

289
  void setDown(const CheckDesc& cd)
290
  {
×
291
    setStatus(cd, false);
×
292
  }
×
293

294
  void setUp(const CheckDesc& cd)
295
  {
×
296
    setStatus(cd, true);
×
297
  }
×
298
};
299

300
bool IsUpOracle::isUp(const CheckDesc& cd)
301
{
×
302
  if (!d_checkerThreadStarted.test_and_set()) {
×
303
    d_checkerThread = std::make_unique<std::thread>([this] { return checkThread(); });
×
304
  }
×
305
  time_t now = time(nullptr);
×
306
  {
×
307
    auto statuses = d_statuses.read_lock();
×
308
    auto iter = statuses->find(cd);
×
309
    if (iter != statuses->end()) {
×
310
      iter->second->lastAccess = now;
×
311
      return iter->second->status;
×
312
    }
×
313
  }
×
314
  // try to parse options so we don't insert any malformed content
315
  if (cd.opts.count("source")) {
×
316
    ComboAddress src(cd.opts.at("source"));
×
317
  }
×
318
  {
×
319
    auto statuses = d_statuses.write_lock();
×
320
    // Make sure we don't insert new entry twice now we have the lock
321
    if (statuses->find(cd) == statuses->end()) {
×
322
      (*statuses)[cd] = std::make_unique<CheckState>(now);
×
323
    }
×
324
  }
×
325
  return false;
×
326
}
×
327

328
bool IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts)
329
{
×
330
  CheckDesc cd{remote, "", opts};
×
331
  return isUp(cd);
×
332
}
×
333

334
bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts)
335
{
×
336
  CheckDesc cd{remote, url, opts};
×
337
  return isUp(cd);
×
338
}
×
339

340
IsUpOracle g_up;
341
namespace {
342
template<typename T, typename C>
343
bool doCompare(const T& var, const std::string& res, const C& cmp)
344
{
×
345
  if(auto country = boost::get<string>(&var))
×
346
    return cmp(*country, res);
×
347

348
  auto countries=boost::get<vector<pair<int,string> > >(&var);
×
349
  for(const auto& country : *countries) {
×
350
    if(cmp(country.second, res))
×
351
      return true;
×
352
  }
×
353
  return false;
×
354
}
×
355
}
356

357
static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
358
{
×
359
  static bool initialized;
×
360
  extern std::function<std::string(const std::string& ip, int)> g_getGeo;
×
361
  if(!g_getGeo) {
×
362
    if(!initialized) {
×
363
      g_log<<Logger::Error<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl;
×
364
      initialized=true;
×
365
    }
×
366
    return "unknown";
×
367
  }
×
368
  else
×
369
    return g_getGeo(ip, (int)qa);
×
370
}
×
371

372
template <typename T>
373
static T pickRandom(const vector<T>& items)
374
{
×
375
  if (items.empty()) {
×
376
    throw std::invalid_argument("The items list cannot be empty");
×
377
  }
×
378
  return items[dns_random(items.size())];
×
379
}
×
380

381
template <typename T>
382
static T pickHashed(const ComboAddress& who, const vector<T>& items)
383
{
×
384
  if (items.empty()) {
×
385
    throw std::invalid_argument("The items list cannot be empty");
×
386
  }
×
387
  ComboAddress::addressOnlyHash aoh;
×
388
  return items[aoh(who) % items.size()];
×
389
}
×
390

391
template <typename T>
392
static T pickWeightedRandom(const vector< pair<int, T> >& items)
393
{
×
394
  if (items.empty()) {
×
395
    throw std::invalid_argument("The items list cannot be empty");
×
396
  }
×
397
  int sum=0;
×
398
  vector< pair<int, T> > pick;
×
399
  pick.reserve(items.size());
×
400

401
  for(auto& i : items) {
×
402
    sum += i.first;
×
403
    pick.emplace_back(sum, i.second);
×
404
  }
×
405

406
  if (sum == 0) {
×
407
    throw std::invalid_argument("The sum of items cannot be zero");
×
408
  }
×
409

410
  int r = dns_random(sum);
×
411
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
412
  return p->second;
×
413
}
×
414

415
template <typename T>
416
static T pickWeightedHashed(const ComboAddress& bestwho, const vector< pair<int, T> >& items)
417
{
×
418
  if (items.empty()) {
×
419
    throw std::invalid_argument("The items list cannot be empty");
×
420
  }
×
421
  int sum=0;
×
422
  vector< pair<int, T> > pick;
×
423
  pick.reserve(items.size());
×
424

425
  for(auto& i : items) {
×
426
    sum += i.first;
×
427
    pick.push_back({sum, i.second});
×
428
  }
×
429

430
  if (sum == 0) {
×
431
    throw std::invalid_argument("The sum of items cannot be zero");
×
432
  }
×
433

434
  ComboAddress::addressOnlyHash aoh;
×
435
  int r = aoh(bestwho) % sum;
×
436
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
437
  return p->second;
×
438
}
×
439

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

450
  for(auto& i : items) {
×
451
    sum += i.first;
×
452
    pick.push_back({sum, i.second});
×
453
  }
×
454

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

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

464
template <typename T>
465
static vector<T> pickRandomSample(int n, const vector<T>& items)
466
{
×
467
  if (items.empty()) {
×
468
    throw std::invalid_argument("The items list cannot be empty");
×
469
  }
×
470

471
  vector<T> pick;
×
472
  pick.reserve(items.size());
×
473

474
  for(auto& item : items) {
×
475
    pick.push_back(item);
×
476
  }
×
477

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

480
  if (count == 0) {
×
481
    return vector<T>();
×
482
  }
×
483

484
  std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
×
485

486
  vector<T> result = {pick.begin(), pick.begin() + count};
×
487
  return result;
×
488
}
×
489

490
static bool getLatLon(const std::string& ip, double& lat, double& lon)
491
{
×
492
  string inp = getGeo(ip, GeoIPInterface::Location);
×
493
  if(inp.empty())
×
494
    return false;
×
495
  lat=atof(inp.c_str());
×
496
  auto pos=inp.find(' ');
×
497
  if(pos != string::npos)
×
498
    lon=atof(inp.c_str() + pos);
×
499
  return true;
×
500
}
×
501

502
static bool getLatLon(const std::string& ip, string& loc)
503
{
×
504
  int latdeg, latmin, londeg, lonmin;
×
505
  double latsec, lonsec;
×
506
  char lathem='X', lonhem='X';
×
507

508
  double lat = 0, lon = 0;
×
509
  if(!getLatLon(ip, lat, lon))
×
510
    return false;
×
511

512
  if(lat > 0) {
×
513
    lathem='N';
×
514
  }
×
515
  else {
×
516
    lat = -lat;
×
517
    lathem='S';
×
518
  }
×
519

520
  if(lon > 0) {
×
521
    lonhem='E';
×
522
  }
×
523
  else {
×
524
    lon = -lon;
×
525
    lonhem='W';
×
526
  }
×
527

528
  latdeg = lat;
×
529
  latmin = (lat - latdeg)*60.0;
×
530
  latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
×
531

532
  londeg = lon;
×
533
  lonmin = (lon - londeg)*60.0;
×
534
  lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
×
535

536
  // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
537

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

540
  loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
×
541
  return true;
×
542
}
×
543

544
static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips)
545
{
×
546
  if (wips.empty()) {
×
547
    throw std::invalid_argument("The IP list cannot be empty");
×
548
  }
×
549
  map<double, vector<ComboAddress> > ranked;
×
550
  double wlat=0, wlon=0;
×
551
  getLatLon(bestwho.toString(), wlat, wlon);
×
552
  //        cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
553
  vector<string> ret;
×
554
  for(const auto& c : wips) {
×
555
    double lat=0, lon=0;
×
556
    getLatLon(c.toString(), lat, lon);
×
557
    //          cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
558
    double latdiff = wlat-lat;
×
559
    double londiff = wlon-lon;
×
560
    if(londiff > 180)
×
561
      londiff = 360 - londiff;
×
562
    double dist2=latdiff*latdiff + londiff*londiff;
×
563
    //          cout<<"    distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
564
    ranked[dist2].push_back(c);
×
565
  }
×
566
  return ranked.begin()->second[dns_random(ranked.begin()->second.size())];
×
567
}
×
568

569
static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid)
570
{
×
571
  static LockGuarded<UeberBackend> s_ub;
×
572

573
  DNSZoneRecord dr;
×
574
  vector<DNSZoneRecord> ret;
×
575
  {
×
576
    auto ub = s_ub.lock();
×
577
    ub->lookup(QType(qtype), name, zoneid);
×
578
    while (ub->get(dr)) {
×
579
      ret.push_back(dr);
×
580
    }
×
581
  }
×
582
  return ret;
×
583
}
×
584

585
static bool getAuth(const DNSName& name, uint16_t qtype, SOAData* soaData)
586
{
×
587
  static LockGuarded<UeberBackend> s_ub;
×
588

589
  {
×
590
    auto ueback = s_ub.lock();
×
591
    return ueback->getAuth(name, qtype, soaData);
×
592
  }
×
593
}
×
594

595
static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
596
{
×
597
  string selector=defaultValue;
×
598
  if(options) {
×
599
    if(options->count(name))
×
600
      selector=options->find(name)->second;
×
601
  }
×
602
  return selector;
×
603
}
×
604

605
static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
606
{
×
607
  vector<ComboAddress> ret;
×
608

609
  if(selector=="all")
×
610
    return candidates;
×
611
  else if(selector=="random")
×
612
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
613
  else if(selector=="pickclosest")
×
614
    ret.emplace_back(pickclosest(bestwho, candidates));
×
615
  else if(selector=="hashed")
×
616
    ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates));
×
617
  else {
×
618
    g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl;
×
619
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
620
  }
×
621

622
  return ret;
×
623
}
×
624

625
static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
626
{
×
627
  vector<string> result;
×
628
  result.reserve(items.size());
×
629

630
  for (const auto& item : items) {
×
631
    result.emplace_back(item.toString());
×
632
  }
×
633

634
  return result;
×
635
}
×
636

637
static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
638
{
×
639
  vector<ComboAddress> result;
×
640
  result.reserve(items.size());
×
641

642
  for(const auto& item : items) {
×
643
    result.emplace_back(ComboAddress(item.second, port));
×
644
  }
×
645

646
  return result;
×
647
}
×
648

649
/**
650
 * Reads and unify single or multiple sets of ips :
651
 * - {'192.0.2.1', '192.0.2.2'}
652
 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
653
 */
654

655
static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
656
{
×
657
  vector<vector<ComboAddress>> candidates;
×
658

659
  if(auto simple = boost::get<iplist_t>(&items)) {
×
660
    vector<ComboAddress> unit = convComboAddressList(*simple, port);
×
661
    candidates.push_back(unit);
×
662
  } else {
×
663
    auto units = boost::get<ipunitlist_t>(items);
×
664
    for(const auto& u : units) {
×
665
      vector<ComboAddress> unit = convComboAddressList(u.second, port);
×
666
      candidates.push_back(unit);
×
667
    }
×
668
  }
×
669
  return candidates;
×
670
}
×
671

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

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

681
  return result;
×
682
}
×
683

684
static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
685
{
×
686
  vector<pair<int,string> > result;
×
687
  result.reserve(items.size());
×
688

689
  for(const auto& item : items) {
×
690
    result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
×
691
  }
×
692

693
  return result;
×
694
}
×
695

696
bool g_LuaRecordSharedState;
697

698
typedef struct AuthLuaRecordContext
699
{
700
  ComboAddress          bestwho;
701
  DNSName               qname;
702
  DNSName               zone;
703
  int                   zoneid;
704
} lua_record_ctx_t;
705

706
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
707

708
/*
709
 *  Holds computed hashes for a given entry
710
 */
711
struct EntryHashesHolder
712
{
713
  std::atomic<size_t> weight;
714
  std::string entry;
715
  SharedLockGuarded<std::vector<unsigned int>> hashes;
716
  std::atomic<time_t> lastUsed;
717

718
  EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
×
719
  }
×
720

721
  bool hashesComputed() {
×
722
    return weight == hashes.read_lock()->size();
×
723
  }
×
724
  void hash() {
×
725
    auto locked = hashes.write_lock();
×
726
    locked->clear();
×
727
    locked->reserve(weight);
×
728
    size_t count = 0;
×
729
    while (count < weight) {
×
730
      auto value = boost::str(boost::format("%s-%d") % entry % count);
×
731
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
732
      auto whash = burtle(reinterpret_cast<const unsigned char*>(value.data()), value.size(), 0);
×
733
      locked->push_back(whash);
×
734
      ++count;
×
735
    }
×
736
    std::sort(locked->begin(), locked->end());
×
737
  }
×
738
};
739

740
using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
741

742
static SharedLockGuarded<std::map<
743
  zone_hashes_key_t, // zoneid qname entry
744
  std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
745
  >>
746
s_zone_hashes;
747

748
static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
749

750
/**
751
 * every ~g_luaConsistentHashesCleanupInterval, do a cleanup to delete entries that haven't been used in the last g_luaConsistentHashesExpireDelay
752
 */
753
static void cleanZoneHashes()
754
{
×
755
  auto now = time(nullptr);
×
756
  if (s_lastConsistentHashesCleanup > (now - g_luaConsistentHashesCleanupInterval)) {
×
757
    return ;
×
758
  }
×
759
  s_lastConsistentHashesCleanup = now;
×
760
  std::vector<zone_hashes_key_t> toDelete{};
×
761
  {
×
762
    auto locked = s_zone_hashes.read_lock();
×
763
    auto someTimeAgo = now - g_luaConsistentHashesExpireDelay;
×
764

765
    for (const auto& [key, entry]: *locked) {
×
766
      if (entry->lastUsed > someTimeAgo) {
×
767
        toDelete.push_back(key);
×
768
      }
×
769
    }
×
770
  }
×
771
  if (!toDelete.empty()) {
×
772
    auto wlocked = s_zone_hashes.write_lock();
×
773
    for (const auto& key : toDelete) {
×
774
      wlocked->erase(key);
×
775
    }
×
776
  }
×
777
}
×
778

779
static std::vector<std::shared_ptr<EntryHashesHolder>> getCHashedEntries(const int zoneId, const std::string& queryName, const std::vector<std::pair<int, std::string>>& items)
780
{
×
781
  std::vector<std::shared_ptr<EntryHashesHolder>> result{};
×
782
  std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
×
783

784
  {
×
785
    time_t now = time(nullptr);
×
786
    auto locked = s_zone_hashes.read_lock();
×
787

788
    for (const auto& [weight, entry]: items) {
×
789
      auto key = std::make_tuple(zoneId, queryName, entry);
×
790
      if (locked->count(key) == 0) {
×
791
        newEntries[key] = std::make_shared<EntryHashesHolder>(weight, entry, now);
×
792
      } else {
×
793
        locked->at(key)->weight = weight;
×
794
        locked->at(key)->lastUsed = now;
×
795
        result.push_back(locked->at(key));
×
796
      }
×
797
    }
×
798
  }
×
799
  if (!newEntries.empty()) {
×
800
    auto wlocked = s_zone_hashes.write_lock();
×
801

802
    for (auto& [key, entry]: newEntries) {
×
803
      result.push_back(entry);
×
804
      (*wlocked)[key] = std::move(entry);
×
805
    }
×
806
  }
×
807

808
  return result;
×
809
}
×
810

811
static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
812
{
×
813
  const auto& zoneId = s_lua_record_ctx->zoneid;
×
814
  const auto queryName = s_lua_record_ctx->qname.toString();
×
815
  unsigned int sel = std::numeric_limits<unsigned int>::max();
×
816
  unsigned int min = std::numeric_limits<unsigned int>::max();
×
817

818
  boost::optional<std::string> ret;
×
819
  boost::optional<std::string> first;
×
820

821
  cleanZoneHashes();
×
822

823
  auto entries = getCHashedEntries(zoneId, queryName, items);
×
824

825
  ComboAddress::addressOnlyHash addrOnlyHash;
×
826
  auto qhash = addrOnlyHash(bestwho);
×
827
  for (const auto& entry : entries) {
×
828
    if (!entry->hashesComputed()) {
×
829
      entry->hash();
×
830
    }
×
831
    {
×
832
      const auto hashes = entry->hashes.read_lock();
×
833
      if (!hashes->empty()) {
×
834
        if (min > *(hashes->begin())) {
×
835
          min = *(hashes->begin());
×
836
          first = entry->entry;
×
837
        }
×
838

839
        auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
×
840
        if (hash_it != hashes->end()) {
×
841
          if (*hash_it < sel) {
×
842
            sel = *hash_it;
×
843
            ret = entry->entry;
×
844
          }
×
845
        }
×
846
      }
×
847
    }
×
848
  }
×
849
  if (ret != boost::none) {
×
850
    return *ret;
×
851
  }
×
852
  if (first != boost::none) {
×
853
    return *first;
×
854
  }
×
855
  return {};
×
856
}
×
857

858
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)
859
{
×
860
  vector<vector<ComboAddress> > candidates;
×
861
  opts_t opts;
×
862
  if(options)
×
863
    opts = *options;
×
864

865
  candidates = convMultiComboAddressList(ips, port);
×
866

867
  for(const auto& unit : candidates) {
×
868
    vector<ComboAddress> available;
×
869
    for(const auto& c : unit) {
×
870
      if (upcheckf(c, opts)) {
×
871
        available.push_back(c);
×
872
      }
×
873
    }
×
874
    if(!available.empty()) {
×
875
      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
876
      return convComboAddressListToString(res);
×
877
    }
×
878
  }
×
879

880
  // All units down, apply backupSelector on all candidates
881
  vector<ComboAddress> ret{};
×
882
  for(const auto& unit : candidates) {
×
883
    ret.insert(ret.end(), unit.begin(), unit.end());
×
884
  }
×
885

886
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
×
887
  return convComboAddressListToString(res);
×
888
}
×
889

890
static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cognitive-complexity)
891
{
×
892
  lua.writeFunction("latlon", []() {
×
893
      double lat = 0, lon = 0;
×
894
      getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
×
895
      return std::to_string(lat)+" "+std::to_string(lon);
×
896
    });
×
897
  lua.writeFunction("latlonloc", []() {
×
898
      string loc;
×
899
      getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
×
900
      return loc;
×
901
  });
×
902
  lua.writeFunction("closestMagic", []() {
×
903
      vector<ComboAddress> candidates;
×
904
      // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
905
      for(auto l : s_lua_record_ctx->qname.getRawLabels()) {
×
906
        boost::replace_all(l, "-", ".");
×
907
        try {
×
908
          candidates.emplace_back(l);
×
909
        } catch (const PDNSException& e) {
×
910
          // no need to continue as we most likely reached the end of the ip list
911
          break ;
×
912
        }
×
913
      }
×
914
      return pickclosest(s_lua_record_ctx->bestwho, candidates).toString();
×
915
    });
×
916
  lua.writeFunction("latlonMagic", [](){
×
917
      auto labels= s_lua_record_ctx->qname.getRawLabels();
×
918
      if(labels.size()<4)
×
919
        return std::string("unknown");
×
920
      double lat = 0, lon = 0;
×
921
      getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
×
922
      return std::to_string(lat)+" "+std::to_string(lon);
×
923
    });
×
924

925

926
  lua.writeFunction("createReverse", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
927
      try {
×
928
        auto labels = s_lua_record_ctx->qname.getRawLabels();
×
929
        if(labels.size()<4)
×
930
          return std::string("unknown");
×
931

932
        vector<ComboAddress> candidates;
×
933

934
        // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
935
        // e["1.2.3.4"]="bert.powerdns.com" then provides an exception
936
        if(e) {
×
937
          ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
×
938
          const auto& uom = *e;
×
939
          for(const auto& c : uom)
×
940
            if(ComboAddress(c.first, 0) == req)
×
941
              return c.second;
×
942
        }
×
943
        boost::format fmt(format);
×
944
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
945
        fmt % labels[3] % labels[2] % labels[1] % labels[0];
×
946

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

949
        boost::format fmt2("%02x%02x%02x%02x");
×
950
        for(int i=3; i>=0; --i)
×
951
          fmt2 % atoi(labels[i].c_str());
×
952

953
        fmt % (fmt2.str());
×
954

955
        return fmt.str();
×
956
      }
×
957
      catch(std::exception& ex) {
×
958
        g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
×
959
      }
×
960
      return std::string("error");
×
961
    });
×
962
  lua.writeFunction("createForward", []() {
×
963
      static string allZerosIP("0.0.0.0");
×
964
      DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
×
965
      // parts is something like ["1", "2", "3", "4", "static"] or
966
      // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
967
      auto parts = rel.getRawLabels();
×
968
      // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
969
      if(parts.size()>=4) {
×
970
        try {
×
971
          ComboAddress ca(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
×
972
          return ca.toString();
×
973
        } catch (const PDNSException &e) {
×
974
          return allZerosIP;
×
975
        }
×
976
      } else if (!parts.empty()) {
×
977
        auto& input = parts.at(0);
×
978

979
        // allow a word without - in front, as long as it does not contain anything that could be a number
980
        size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
×
981
        if (nonhexprefix > 0) {
×
982
          input = input.substr(nonhexprefix);
×
983
        }
×
984

985
        // either hex string, or 12-13-14-15
986
        vector<string> ip_parts;
×
987

988
        stringtok(ip_parts, input, "-");
×
989
        unsigned int x1, x2, x3, x4;
×
990
        if (ip_parts.size() >= 4) {
×
991
          // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
992
          string ret;
×
993
          for (size_t index=4; index > 0; index--) {
×
994
            auto octet = ip_parts[ip_parts.size() - index];
×
995
            try {
×
996
              auto octetVal = std::stol(octet);
×
997
              if (octetVal >= 0 && octetVal <= 255) {
×
998
                ret += ip_parts.at(ip_parts.size() - index) + ".";
×
999
              } else {
×
1000
                return allZerosIP;
×
1001
              }
×
1002
            } catch (const std::exception &e) {
×
1003
              return allZerosIP;
×
1004
            }
×
1005
          }
×
1006
          ret.resize(ret.size() - 1); // remove trailing dot after last octet
×
1007
          return ret;
×
1008
        }
×
1009
        if(input.length() >= 8) {
×
1010
          auto last8 = input.substr(input.length()-8);
×
1011
          if(sscanf(last8.c_str(), "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
×
1012
            return std::to_string(x1) + "." + std::to_string(x2) + "." + std::to_string(x3) + "." + std::to_string(x4);
×
1013
          }
×
1014
        }
×
1015
      }
×
1016
      return allZerosIP;
×
1017
    });
×
1018

1019
  lua.writeFunction("createForward6", []() {
×
1020
      DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
×
1021
      auto parts = rel.getRawLabels();
×
1022
      if(parts.size()==8) {
×
1023
        string tot;
×
1024
        for(int i=0; i<8; ++i) {
×
1025
          if(i)
×
1026
            tot.append(1,':');
×
1027
          tot+=parts[i];
×
1028
        }
×
1029
        ComboAddress ca(tot);
×
1030
        return ca.toString();
×
1031
      }
×
1032
      else if(parts.size()==1) {
×
1033
        if (parts[0].find('-') != std::string::npos) {
×
1034
          boost::replace_all(parts[0],"-",":");
×
1035
          ComboAddress ca(parts[0]);
×
1036
          return ca.toString();
×
1037
        } else {
×
1038
          if (parts[0].size() >= 32) {
×
1039
            auto ippart = parts[0].substr(parts[0].size()-32);
×
1040
            auto fulladdress =
×
1041
              ippart.substr(0, 4) + ":" +
×
1042
              ippart.substr(4, 4) + ":" +
×
1043
              ippart.substr(8, 4) + ":" +
×
1044
              ippart.substr(12, 4) + ":" +
×
1045
              ippart.substr(16, 4) + ":" +
×
1046
              ippart.substr(20, 4) + ":" +
×
1047
              ippart.substr(24, 4) + ":" +
×
1048
              ippart.substr(28, 4);
×
1049

1050
            ComboAddress ca(fulladdress);
×
1051
            return ca.toString();
×
1052
          }
×
1053
        }
×
1054
      }
×
1055

1056
      return std::string("::");
×
1057
    });
×
1058
  lua.writeFunction("createReverse6", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
1059
      vector<ComboAddress> candidates;
×
1060

1061
      try {
×
1062
        auto labels= s_lua_record_ctx->qname.getRawLabels();
×
1063
        if(labels.size()<32)
×
1064
          return std::string("unknown");
×
1065
        boost::format fmt(format);
×
1066
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
1067

1068

1069
        string together;
×
1070
        vector<string> quads;
×
1071
        for(int i=0; i<8; ++i) {
×
1072
          if(i)
×
1073
            together+=":";
×
1074
          string lquad;
×
1075
          for(int j=0; j <4; ++j) {
×
1076
            lquad.append(1, labels[31-i*4-j][0]);
×
1077
            together += labels[31-i*4-j][0];
×
1078
          }
×
1079
          quads.push_back(lquad);
×
1080
        }
×
1081
        ComboAddress ip6(together,0);
×
1082

1083
        if(e) {
×
1084
          auto& addrs=*e;
×
1085
          for(const auto& addr: addrs) {
×
1086
            // this makes sure we catch all forms of the address
1087
            if(ComboAddress(addr.first,0)==ip6)
×
1088
              return addr.second;
×
1089
          }
×
1090
        }
×
1091

1092
        string dashed=ip6.toString();
×
1093
        boost::replace_all(dashed, ":", "-");
×
1094

1095
        for(int i=31; i>=0; --i)
×
1096
          fmt % labels[i];
×
1097
        fmt % dashed;
×
1098

1099
        for(const auto& lquad : quads)
×
1100
          fmt % lquad;
×
1101

1102
        return fmt.str();
×
1103
      }
×
1104
      catch(std::exception& ex) {
×
1105
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.what()<<endl;
×
1106
      }
×
1107
      catch(PDNSException& ex) {
×
1108
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl;
×
1109
      }
×
1110
      return std::string("unknown");
×
1111
    });
×
1112

1113
  lua.writeFunction("filterForward", [](const string& address, NetmaskGroup& nmg, boost::optional<string> fallback) -> vector<string> {
×
1114
      ComboAddress ca(address);
×
1115

1116
      if (nmg.match(ComboAddress(address))) {
×
1117
        return {address};
×
1118
      } else {
×
1119
        if (fallback) {
×
1120
          if (fallback->empty()) {
×
1121
            // if fallback is an empty string, return an empty array
1122
            return {};
×
1123
          }
×
1124
          return {*fallback};
×
1125
        }
×
1126

1127
        if (ca.isIPv4()) {
×
1128
          return {string("0.0.0.0")};
×
1129
        } else {
×
1130
          return {string("::")};
×
1131
        }
×
1132
      }
×
1133
    });
×
1134

1135
  /*
1136
   * Simplistic test to see if an IP address listens on a certain port
1137
   * Will return a single IP address from the set of available IP addresses. If
1138
   * no IP address is available, will return a random element of the set of
1139
   * addresses supplied for testing.
1140
   *
1141
   * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
1142
   */
1143
  lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, const boost::optional<std::unordered_map<string,string>> options) {
×
1144
      if (port < 0) {
×
1145
        port = 0;
×
1146
      }
×
1147
      if (port > std::numeric_limits<uint16_t>::max()) {
×
1148
        port = std::numeric_limits<uint16_t>::max();
×
1149
      }
×
1150

1151
      auto checker = [](const ComboAddress& addr, const opts_t& opts) {
×
1152
        return g_up.isUp(addr, opts);
×
1153
      };
×
1154
      return genericIfUp(ips, options, checker, port);
×
1155
    });
×
1156

1157
  lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) {
×
1158
      vector<ComboAddress> candidates;
×
1159
      opts_t opts;
×
1160
      if(options)
×
1161
        opts = *options;
×
1162

1163
      ComboAddress ca_unspec;
×
1164
      ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1165

1166
      // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1167
      for (const auto& [count, unitmap] : ipurls) {
×
1168
        // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1169
        vector<ComboAddress> available;
×
1170

1171
        for (const auto& [ipStr, url] : unitmap) {
×
1172
          // unit: ["192.0.2.1"] = "https://example.com"
1173
          ComboAddress ip(ipStr);
×
1174
          candidates.push_back(ip);
×
1175
          if (g_up.isUp(ca_unspec, url, opts)) {
×
1176
            available.push_back(ip);
×
1177
          }
×
1178
        }
×
1179
        if(!available.empty()) {
×
1180
          vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
1181
          return convComboAddressListToString(res);
×
1182
        }
×
1183
      }
×
1184

1185
      // All units down, apply backupSelector on all candidates
1186
      vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
×
1187
      return convComboAddressListToString(res);
×
1188
    });
×
1189

1190
  lua.writeFunction("ifurlup", [](const std::string& url,
×
1191
                                          const boost::variant<iplist_t, ipunitlist_t>& ips,
×
1192
                                          boost::optional<opts_t> options) {
×
1193

1194
    auto checker = [&url](const ComboAddress& addr, const opts_t& opts) {
×
1195
        return g_up.isUp(addr, url, opts);
×
1196
      };
×
1197
      return genericIfUp(ips, options, checker);
×
1198
    });
×
1199
  /*
1200
   * Returns a random IP address from the supplied list
1201
   * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
1202
   */
1203
  lua.writeFunction("pickrandom", [](const iplist_t& ips) {
×
1204
      vector<string> items = convStringList(ips);
×
1205
      return pickRandom<string>(items);
×
1206
    });
×
1207

1208
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
×
1209
      vector<string> items = convStringList(ips);
×
1210
          return pickRandomSample<string>(n, items);
×
1211
    });
×
1212

1213
  lua.writeFunction("pickhashed", [](const iplist_t& ips) {
×
1214
      vector<string> items = convStringList(ips);
×
1215
      return pickHashed<string>(s_lua_record_ctx->bestwho, items);
×
1216
    });
×
1217
  /*
1218
   * Returns a random IP address from the supplied list, as weighted by the
1219
   * various ``weight`` parameters
1220
   * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1221
   */
1222
  lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
×
1223
      vector< pair<int, string> > items = convIntStringPairList(ips);
×
1224
      return pickWeightedRandom<string>(items);
×
1225
    });
×
1226

1227
  /*
1228
   * Based on the hash of `bestwho`, returns an IP address from the list
1229
   * supplied, as weighted by the various `weight` parameters
1230
   * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1231
   */
1232
  lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) {
×
1233
      vector< pair<int, string> > items;
×
1234

1235
      items.reserve(ips.size());
×
1236
      for (auto& entry : ips) {
×
1237
        items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
×
1238
      }
×
1239

1240
      return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1241
    });
×
1242

1243
  /*
1244
   * Based on the hash of the record name, return an IP address from the list
1245
   * supplied, as weighted by the various `weight` parameters
1246
   * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1247
   */
1248
  lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t > ips) {
×
1249
      vector< pair<int, string> > items;
×
1250

1251
      items.reserve(ips.size());
×
1252
      for(auto& i : ips)
×
1253
      {
×
1254
        items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
×
1255
      }
×
1256

1257
      return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
×
1258
    });
×
1259
  /*
1260
   * Based on the hash of `bestwho`, returns an IP address from the list
1261
   * supplied, as weighted by the various `weight` parameters and distributed consistently
1262
   * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1263
   */
1264
  lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) {
×
1265
    std::vector<std::pair<int, std::string>> items;
×
1266

1267
    items.reserve(ips.size());
×
1268
    for (const auto& entry : ips) {
×
1269
      items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
×
1270
    }
×
1271

1272
    return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1273
  });
×
1274

1275
  lua.writeFunction("pickclosest", [](const iplist_t& ips) {
×
1276
      vector<ComboAddress> conv = convComboAddressList(ips);
×
1277

1278
      return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1279

1280
    });
×
1281

1282
  if (g_luaRecordExecLimit > 0) {
×
1283
      lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1284
  }
×
1285

1286
  lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){
×
1287
      throw std::runtime_error("Script took too long");
×
1288
    });
×
1289

1290
  lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
×
1291
    return getGeo(ip, attr);
×
1292
  });
×
1293

1294
  typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
×
1295

1296
  lua.writeFunction("asnum", [](const combovar_t& asns) {
×
1297
      string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
×
1298
      return doCompare(asns, res, [](const std::string& a, const std::string& b) {
×
1299
          return !strcasecmp(a.c_str(), b.c_str());
×
1300
        });
×
1301
    });
×
1302
  lua.writeFunction("continent", [](const combovar_t& continent) {
×
1303
     string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1304
      return doCompare(continent, res, [](const std::string& a, const std::string& b) {
×
1305
          return !strcasecmp(a.c_str(), b.c_str());
×
1306
        });
×
1307
    });
×
1308
  lua.writeFunction("continentCode", []() {
×
1309
      string unknown("unknown");
×
1310
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1311
      if ( res == unknown ) {
×
1312
       return std::string("--");
×
1313
      }
×
1314
      return res;
×
1315
    });
×
1316
  lua.writeFunction("country", [](const combovar_t& var) {
×
1317
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1318
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1319
          return !strcasecmp(a.c_str(), b.c_str());
×
1320
        });
×
1321

1322
    });
×
1323
  lua.writeFunction("countryCode", []() {
×
1324
      string unknown("unknown");
×
1325
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1326
      if ( res == unknown ) {
×
1327
       return std::string("--");
×
1328
      }
×
1329
      return res;
×
1330
    });
×
1331
  lua.writeFunction("region", [](const combovar_t& var) {
×
1332
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1333
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1334
          return !strcasecmp(a.c_str(), b.c_str());
×
1335
        });
×
1336

1337
    });
×
1338
  lua.writeFunction("regionCode", []() {
×
1339
      string unknown("unknown");
×
1340
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1341
      if ( res == unknown ) {
×
1342
       return std::string("--");
×
1343
      }
×
1344
      return res;
×
1345
    });
×
1346
  lua.writeFunction("netmask", [](const iplist_t& ips) {
×
1347
      for(const auto& i :ips) {
×
1348
        Netmask nm(i.second);
×
1349
        if(nm.match(s_lua_record_ctx->bestwho))
×
1350
          return true;
×
1351
      }
×
1352
      return false;
×
1353
    });
×
1354
  /* {
1355
       {
1356
        {'192.168.0.0/16', '10.0.0.0/8'},
1357
        {'192.168.20.20', '192.168.20.21'}
1358
       },
1359
       {
1360
        {'0.0.0.0/0'}, {'192.0.2.1'}
1361
       }
1362
     }
1363
  */
1364
  lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) {
×
1365
      for(const auto& rule : in) {
×
1366
        const auto& netmasks=rule.second[0].second;
×
1367
        const auto& destinations=rule.second[1].second;
×
1368
        for(const auto& nmpair : netmasks) {
×
1369
          Netmask nm(nmpair.second);
×
1370
          if(nm.match(s_lua_record_ctx->bestwho)) {
×
1371
            if (destinations.empty()) {
×
1372
              throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm.toString() + ")");
×
1373
            }
×
1374
            return destinations[dns_random(destinations.size())].second;
×
1375
          }
×
1376
        }
×
1377
      }
×
1378
      return std::string();
×
1379
    });
×
1380

1381
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
×
1382
      vector<string> result;
×
1383
          result.reserve(ips.size());
×
1384

1385
      for(const auto& ip : ips) {
×
1386
          result.emplace_back(ip.second);
×
1387
      }
×
1388
      if(result.empty()) {
×
1389
        throw std::invalid_argument("The IP list cannot be empty");
×
1390
      }
×
1391
      return result;
×
1392
    });
×
1393

1394
  lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) {
×
1395
    DNSName rec;
×
1396
    vector<string> ret;
×
1397
    try {
×
1398
      rec = DNSName(record);
×
1399
    }
×
1400
    catch (const std::exception& e) {
×
1401
      g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
×
1402
      return ret;
×
1403
    }
×
1404
    try {
×
1405
      SOAData soaData;
×
1406

1407
      if (!getAuth(rec, qtype, &soaData)) {
×
1408
        return ret;
×
1409
      }
×
1410

1411
      vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id);
×
1412
      for (const auto& drec : drs) {
×
1413
        ret.push_back(drec.dr.getContent()->getZoneRepresentation());
×
1414
      }
×
1415
    }
×
1416
    catch (std::exception& e) {
×
1417
      g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
×
1418
    }
×
1419
    return ret;
×
1420
  });
×
1421

1422
  lua.writeFunction("include", [&lua](string record) {
×
1423
      DNSName rec;
×
1424
      try {
×
1425
        rec = DNSName(record) + s_lua_record_ctx->zone;
×
1426
      } catch (const std::exception &e){
×
1427
        g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl;
×
1428
        return;
×
1429
      }
×
1430
      try {
×
1431
        vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zoneid);
×
1432
        for(const auto& dr : drs) {
×
1433
          auto lr = getRR<LUARecordContent>(dr.dr);
×
1434
          lua.executeCode(lr->getCode());
×
1435
        }
×
1436
      }
×
1437
      catch(std::exception& e) {
×
1438
        g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<rec<<": "<<e.what()<<endl;
×
1439
      }
×
1440
    });
×
1441
}
×
1442

1443
std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype, unique_ptr<AuthLua4>& LUA)
1444
{
×
1445
  if(!LUA ||                  // we don't have a Lua state yet
×
1446
     !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1447
    LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
×
1448
    setupLuaRecords(*LUA->getLua());
×
1449
  }
×
1450

1451
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1452

1453
  LuaContext& lua = *LUA->getLua();
×
1454

1455
  s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1456
  s_lua_record_ctx->qname = query;
×
1457
  s_lua_record_ctx->zone = zone;
×
1458
  s_lua_record_ctx->zoneid = zoneid;
×
1459

1460
  lua.writeVariable("qname", query);
×
1461
  lua.writeVariable("zone", zone);
×
1462
  lua.writeVariable("zoneid", zoneid);
×
1463
  lua.writeVariable("who", dnsp.getInnerRemote());
×
1464
  lua.writeVariable("localwho", dnsp.getLocal());
×
1465
  lua.writeVariable("dh", (dnsheader*)&dnsp.d);
×
1466
  lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
×
1467
  lua.writeVariable("tcp", dnsp.d_tcp);
×
1468
  lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
×
1469
  if(dnsp.hasEDNSSubnet()) {
×
1470
    lua.writeVariable("ecswho", dnsp.getRealRemote());
×
1471
    s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
×
1472
  }
×
1473
  else {
×
1474
    lua.writeVariable("ecswho", nullptr);
×
1475
    s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
×
1476
  }
×
1477
  lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
×
1478

1479
  try {
×
1480
    string actual;
×
1481
    if(!code.empty() && code[0]!=';')
×
1482
      actual = "return " + code;
×
1483
    else
×
1484
      actual = code.substr(1);
×
1485

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

1488
    vector<string> contents;
×
1489
    if(auto str = boost::get<string>(&content))
×
1490
      contents.push_back(*str);
×
1491
    else
×
1492
      for(const auto& c : boost::get<vector<pair<int,string>>>(content))
×
1493
        contents.push_back(c.second);
×
1494

1495
    for(const auto& content_it: contents) {
×
1496
      if(qtype==QType::TXT)
×
1497
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
×
1498
      else
×
1499
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
×
1500
    }
×
1501
  } catch(std::exception &e) {
×
1502
    g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
×
1503
    try {
×
1504
      std::rethrow_if_nested(e);
×
1505
      g_log<<endl;
×
1506
    } catch(const std::exception& ne) {
×
1507
      g_log << ": " << ne.what() << std::endl;
×
1508
    }
×
1509
    catch(const PDNSException& ne) {
×
1510
      g_log << ": " << ne.reason << std::endl;
×
1511
    }
×
1512
    throw ;
×
1513
  }
×
1514

1515
  return ret;
×
1516
}
×
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