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

PowerDNS / pdns / 12468988911

23 Dec 2024 03:16PM UTC coverage: 64.74% (-0.07%) from 64.807%
12468988911

Pull #14996

github

web-flow
Merge 2f087f3b7 into f244055ce
Pull Request #14996: Lua up checks: finer control

37681 of 88998 branches covered (42.34%)

Branch coverage included in aggregate %.

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

229 existing lines in 19 files now uncovered.

126068 of 163934 relevant lines covered (76.9%)

4336455.94 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());
×
196

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

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

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

NEW
239
      std::this_thread::sleep_until(checkStart + std::chrono::seconds(interval));
×
240
    }
×
241
  }
×
242

243
  typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
244
  SharedLockGuarded<statuses_t> d_statuses;
245

246
  std::unique_ptr<std::thread> d_checkerThread;
247
  std::atomic_flag d_checkerThreadStarted;
248

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

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

279
  void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
280
  {
×
281
    CheckDesc cd{rem, url, opts};
×
282

×
283
    setStatus(cd, true);
×
284
  }
×
285

286
  void setDown(const CheckDesc& cd)
287
  {
×
288
    setStatus(cd, false);
×
289
  }
×
290

291
  void setUp(const CheckDesc& cd)
292
  {
×
293
    setStatus(cd, true);
×
294
  }
×
295
};
296

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

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

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

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

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

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

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

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

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

398
  for(auto& i : items) {
×
399
    sum += i.first;
×
400
    pick.emplace_back(sum, i.second);
×
401
  }
×
402

403
  if (sum == 0) {
×
404
    throw std::invalid_argument("The sum of items cannot be zero");
×
405
  }
×
406

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

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

422
  for(auto& i : items) {
×
423
    sum += i.first;
×
424
    pick.push_back({sum, i.second});
×
425
  }
×
426

427
  if (sum == 0) {
×
428
    throw std::invalid_argument("The sum of items cannot be zero");
×
429
  }
×
430

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

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

447
  for(auto& i : items) {
×
448
    sum += i.first;
×
449
    pick.push_back({sum, i.second});
×
450
  }
×
451

452
  if (sum == 0) {
×
453
    throw std::invalid_argument("The sum of items cannot be zero");
×
454
  }
×
455

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

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

468
  vector<T> pick;
×
469
  pick.reserve(items.size());
×
470

471
  for(auto& item : items) {
×
472
    pick.push_back(item);
×
473
  }
×
474

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

477
  if (count == 0) {
×
478
    return vector<T>();
×
479
  }
×
480

481
  std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
×
482

483
  vector<T> result = {pick.begin(), pick.begin() + count};
×
484
  return result;
×
485
}
×
486

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

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

505
  double lat = 0, lon = 0;
×
506
  if(!getLatLon(ip, lat, lon))
×
507
    return false;
×
508

509
  if(lat > 0) {
×
510
    lathem='N';
×
511
  }
×
512
  else {
×
513
    lat = -lat;
×
514
    lathem='S';
×
515
  }
×
516

517
  if(lon > 0) {
×
518
    lonhem='E';
×
519
  }
×
520
  else {
×
521
    lon = -lon;
×
522
    lonhem='W';
×
523
  }
×
524

525
  latdeg = lat;
×
526
  latmin = (lat - latdeg)*60.0;
×
527
  latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
×
528

529
  londeg = lon;
×
530
  lonmin = (lon - londeg)*60.0;
×
531
  lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
×
532

533
  // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
534

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

537
  loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
×
538
  return true;
×
539
}
×
540

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

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

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

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

586
  {
×
587
    auto ueback = s_ub.lock();
×
588
    return ueback->getAuth(name, qtype, soaData);
×
589
  }
×
590
}
×
591

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

602
static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
603
{
×
604
  vector<ComboAddress> ret;
×
605

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

619
  return ret;
×
620
}
×
621

622
static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
623
{
×
624
  vector<string> result;
×
625
  result.reserve(items.size());
×
626

627
  for (const auto& item : items) {
×
628
    result.emplace_back(item.toString());
×
629
  }
×
630

631
  return result;
×
632
}
×
633

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

639
  for(const auto& item : items) {
×
640
    result.emplace_back(ComboAddress(item.second, port));
×
641
  }
×
642

643
  return result;
×
644
}
×
645

646
/**
647
 * Reads and unify single or multiple sets of ips :
648
 * - {'192.0.2.1', '192.0.2.2'}
649
 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
650
 */
651

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

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

669
static vector<string> convStringList(const iplist_t& items)
670
{
×
671
  vector<string> result;
×
672
  result.reserve(items.size());
×
673

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

678
  return result;
×
679
}
×
680

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

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

690
  return result;
×
691
}
×
692

693
bool g_LuaRecordSharedState;
694

695
typedef struct AuthLuaRecordContext
696
{
697
  ComboAddress          bestwho;
698
  DNSName               qname;
699
  DNSName               zone;
700
  int                   zoneid;
701
} lua_record_ctx_t;
702

703
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
704

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

715
  EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
×
716
  }
×
717

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

737
using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
738

739
static SharedLockGuarded<std::map<
740
  zone_hashes_key_t, // zoneid qname entry
741
  std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
742
  >>
743
s_zone_hashes;
744

745
static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
746

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

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

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

781
  {
×
782
    time_t now = time(nullptr);
×
783
    auto locked = s_zone_hashes.read_lock();
×
784

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

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

805
  return result;
×
806
}
×
807

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

815
  boost::optional<std::string> ret;
×
816
  boost::optional<std::string> first;
×
817

818
  cleanZoneHashes();
×
819

820
  auto entries = getCHashedEntries(zoneId, queryName, items);
×
821

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

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

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

862
  candidates = convMultiComboAddressList(ips, port);
×
863

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

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

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

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

922

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

929
        vector<ComboAddress> candidates;
×
930

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

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

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

950
        fmt % (fmt2.str());
×
951

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

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

982
        // either hex string, or 12-13-14-15
983
        vector<string> ip_parts;
×
984

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

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

1047
            ComboAddress ca(fulladdress);
×
1048
            return ca.toString();
×
1049
          }
×
1050
        }
×
1051
      }
×
1052

1053
      return std::string("::");
×
1054
    });
×
NEW
1055
  lua.writeFunction("createReverse6", [](string format, boost::optional<opts_t> e){
×
1056
      vector<ComboAddress> candidates;
×
1057

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

1065

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

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

1089
        string dashed=ip6.toString();
×
1090
        boost::replace_all(dashed, ":", "-");
×
1091

1092
        for(int i=31; i>=0; --i)
×
1093
          fmt % labels[i];
×
1094
        fmt % dashed;
×
1095

1096
        for(const auto& lquad : quads)
×
1097
          fmt % lquad;
×
1098

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

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

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

1124
        if (ca.isIPv4()) {
×
1125
          return {string("0.0.0.0")};
×
1126
        } else {
×
1127
          return {string("::")};
×
1128
        }
×
1129
      }
×
1130
    });
×
1131

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

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

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

1160
      ComboAddress ca_unspec;
×
1161
      ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1162

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

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

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

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

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

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

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

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

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

1237
      return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1238
    });
×
1239

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

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

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

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

1269
    return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1270
  });
×
1271

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

1275
      return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1276

1277
    });
×
1278

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

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

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

1291
  typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
×
1292

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

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

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

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

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

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

1404
      if (!getAuth(rec, qtype, &soaData)) {
×
1405
        return ret;
×
1406
      }
×
1407

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

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

1440
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)
1441
{
×
1442
  if(!LUA ||                  // we don't have a Lua state yet
×
1443
     !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1444
    LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
×
1445
    setupLuaRecords(*LUA->getLua());
×
1446
  }
×
1447

1448
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1449

1450
  LuaContext& lua = *LUA->getLua();
×
1451

1452
  s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1453
  s_lua_record_ctx->qname = query;
×
1454
  s_lua_record_ctx->zone = zone;
×
1455
  s_lua_record_ctx->zoneid = zoneid;
×
1456

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

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

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

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

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

1512
  return ret;
×
1513
}
×
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