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

PowerDNS / pdns / 12744987305

13 Jan 2025 10:12AM UTC coverage: 64.903% (-0.01%) from 64.915%
12744987305

push

github

web-flow
Merge pull request #14996 from miodvallat/lua_finer_intervals

Lua up checks: finer control

37830 of 89122 branches covered (42.45%)

Branch coverage included in aggregate %.

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

52 existing lines in 13 files now uncovered.

126324 of 163799 relevant lines covered (77.12%)

4620966.52 hits per line

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

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

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

24
   investigate IPv6
25

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

28
   ponder ECS scopemask setting
29

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

32
   add attribute for certificate check in genericIfUp
33

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

37
   pool of UeberBackends?
38

39
   Pool checks ?
40
 */
41

42
extern int  g_luaRecordExecLimit;
43

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

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

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

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

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

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

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

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

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

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

NEW
209
          if (desc.opts.count("interval") != 0) {
×
NEW
210
            checkInterval = std::atoi(desc.opts.at("interval").c_str());
×
NEW
211
            if (checkInterval != 0) {
×
NEW
212
              interval = std::gcd(interval, checkInterval);
×
NEW
213
            }
×
NEW
214
          }
×
215

NEW
216
          if (not state->first) {
×
NEW
217
            time_t nextCheckSecond = state->lastStatusUpdate;
×
NEW
218
            if (checkInterval != 0) {
×
NEW
219
               nextCheckSecond += checkInterval;
×
NEW
220
            }
×
NEW
221
            else {
×
NEW
222
               nextCheckSecond += g_luaHealthChecksInterval;
×
NEW
223
            }
×
NEW
224
            if (checkStart < std::chrono::system_clock::from_time_t(nextCheckSecond)) {
×
NEW
225
              continue; // too early
×
NEW
226
            }
×
NEW
227
          }
×
228

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

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

NEW
257
      std::this_thread::sleep_until(checkStart + std::chrono::seconds(interval));
×
258
    }
×
259
  }
×
260

261
  typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
262
  SharedLockGuarded<statuses_t> d_statuses;
263

264
  std::unique_ptr<std::thread> d_checkerThread;
265
  std::atomic_flag d_checkerThreadStarted;
266

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

291
  //NOLINTNEXTLINE(readability-identifier-length)
292
  void setWeight(const CheckDesc& cd, int weight){
×
293
    auto statuses = d_statuses.write_lock();
×
294
    auto& state = (*statuses)[cd];
×
295
    state->weight = weight;
×
296
  }
×
297

298
  void setDown(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
299
  {
×
300
    //NOLINTNEXTLINE(readability-identifier-length)
×
301
    CheckDesc cd{rem, url, opts};
×
302
    setStatus(cd, false);
×
303
  }
×
304

305
  void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
306
  {
×
307
    CheckDesc cd{rem, url, opts};
×
308

×
309
    setStatus(cd, true);
×
310
  }
×
311

312
  void setDown(const CheckDesc& cd)
313
  {
×
314
    setStatus(cd, false);
×
315
  }
×
316

317
  void setUp(const CheckDesc& cd)
318
  {
×
319
    setStatus(cd, true);
×
320
  }
×
321
};
322

323
//NOLINTNEXTLINE(readability-identifier-length)
324
int IsUpOracle::isUp(const CheckDesc& cd)
325
{
×
326
  if (!d_checkerThreadStarted.test_and_set()) {
×
327
    d_checkerThread = std::make_unique<std::thread>([this] { return checkThread(); });
×
328
  }
×
329
  time_t now = time(nullptr);
×
330
  {
×
331
    auto statuses = d_statuses.read_lock();
×
332
    auto iter = statuses->find(cd);
×
333
    if (iter != statuses->end()) {
×
334
      iter->second->lastAccess = now;
×
335
      if (iter->second->weight > 0) {
×
336
        return iter->second->weight;
×
337
      }
×
338
      return static_cast<int>(iter->second->status);
×
339
    }
×
340
  }
×
341
  // try to parse options so we don't insert any malformed content
342
  if (cd.opts.count("source")) {
×
343
    ComboAddress src(cd.opts.at("source"));
×
344
  }
×
345
  {
×
346
    auto statuses = d_statuses.write_lock();
×
347
    // Make sure we don't insert new entry twice now we have the lock
348
    if (statuses->find(cd) == statuses->end()) {
×
349
      (*statuses)[cd] = std::make_unique<CheckState>(now);
×
350
    }
×
351
  }
×
352
  return 0;
×
353
}
×
354

355
int IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts)
356
{
×
357
  CheckDesc cd{remote, "", opts};
×
358
  return isUp(cd);
×
359
}
×
360

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

367
IsUpOracle g_up;
368
namespace {
369
template<typename T, typename C>
370
bool doCompare(const T& var, const std::string& res, const C& cmp)
371
{
×
372
  if(auto country = boost::get<string>(&var))
×
373
    return cmp(*country, res);
×
374

375
  auto countries=boost::get<vector<pair<int,string> > >(&var);
×
376
  for(const auto& country : *countries) {
×
377
    if(cmp(country.second, res))
×
378
      return true;
×
379
  }
×
380
  return false;
×
381
}
×
382
}
383

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

399
template <typename T>
400
static T pickRandom(const vector<T>& items)
401
{
×
402
  if (items.empty()) {
×
403
    throw std::invalid_argument("The items list cannot be empty");
×
404
  }
×
405
  return items[dns_random(items.size())];
×
406
}
×
407

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

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

428
  for(auto& i : items) {
×
429
    sum += i.first;
×
430
    pick.emplace_back(sum, i.second);
×
431
  }
×
432

433
  if (sum == 0) {
×
434
    throw std::invalid_argument("The sum of items cannot be zero");
×
435
  }
×
436

437
  int r = dns_random(sum);
×
438
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
439
  return p->second;
×
440
}
×
441

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

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

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

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

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

477
  for(auto& i : items) {
×
478
    sum += i.first;
×
479
    pick.push_back({sum, i.second});
×
480
  }
×
481

482
  if (sum == 0) {
×
483
    throw std::invalid_argument("The sum of items cannot be zero");
×
484
  }
×
485

486
  size_t r = dnsname.hash() % sum;
×
487
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
488
  return p->second;
×
489
}
×
490

491
template <typename T>
492
static vector<T> pickRandomSample(int n, const vector<T>& items)
493
{
×
494
  if (items.empty()) {
×
495
    throw std::invalid_argument("The items list cannot be empty");
×
496
  }
×
497

498
  vector<T> pick;
×
499
  pick.reserve(items.size());
×
500

501
  for(auto& item : items) {
×
502
    pick.push_back(item);
×
503
  }
×
504

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

507
  if (count == 0) {
×
508
    return vector<T>();
×
509
  }
×
510

511
  std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
×
512

513
  vector<T> result = {pick.begin(), pick.begin() + count};
×
514
  return result;
×
515
}
×
516

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

529
static bool getLatLon(const std::string& ip, string& loc)
530
{
×
531
  int latdeg, latmin, londeg, lonmin;
×
532
  double latsec, lonsec;
×
533
  char lathem='X', lonhem='X';
×
534

535
  double lat = 0, lon = 0;
×
536
  if(!getLatLon(ip, lat, lon))
×
537
    return false;
×
538

539
  if(lat > 0) {
×
540
    lathem='N';
×
541
  }
×
542
  else {
×
543
    lat = -lat;
×
544
    lathem='S';
×
545
  }
×
546

547
  if(lon > 0) {
×
548
    lonhem='E';
×
549
  }
×
550
  else {
×
551
    lon = -lon;
×
552
    lonhem='W';
×
553
  }
×
554

555
  latdeg = lat;
×
556
  latmin = (lat - latdeg)*60.0;
×
557
  latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
×
558

559
  londeg = lon;
×
560
  lonmin = (lon - londeg)*60.0;
×
561
  lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
×
562

563
  // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
564

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

567
  loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
×
568
  return true;
×
569
}
×
570

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

596
static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid)
597
{
×
598
  static LockGuarded<UeberBackend> s_ub;
×
599

600
  DNSZoneRecord dr;
×
601
  vector<DNSZoneRecord> ret;
×
602
  {
×
603
    auto ub = s_ub.lock();
×
604
    ub->lookup(QType(qtype), name, zoneid);
×
605
    while (ub->get(dr)) {
×
606
      ret.push_back(dr);
×
607
    }
×
608
  }
×
609
  return ret;
×
610
}
×
611

612
static bool getAuth(const DNSName& name, uint16_t qtype, SOAData* soaData)
613
{
×
614
  static LockGuarded<UeberBackend> s_ub;
×
615

616
  {
×
617
    auto ueback = s_ub.lock();
×
618
    return ueback->getAuth(name, qtype, soaData);
×
619
  }
×
620
}
×
621

622
static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
623
{
×
624
  string selector=defaultValue;
×
625
  if(options) {
×
626
    if(options->count(name))
×
627
      selector=options->find(name)->second;
×
628
  }
×
629
  return selector;
×
630
}
×
631

632
static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
633
{
×
634
  vector<ComboAddress> ret;
×
635

636
  if(selector=="all")
×
637
    return candidates;
×
638
  else if(selector=="random")
×
639
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
640
  else if(selector=="pickclosest")
×
641
    ret.emplace_back(pickclosest(bestwho, candidates));
×
642
  else if(selector=="hashed")
×
643
    ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates));
×
644
  else {
×
645
    g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl;
×
646
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
647
  }
×
648

649
  return ret;
×
650
}
×
651

652
static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
653
{
×
654
  vector<string> result;
×
655
  result.reserve(items.size());
×
656

657
  for (const auto& item : items) {
×
658
    result.emplace_back(item.toString());
×
659
  }
×
660

661
  return result;
×
662
}
×
663

664
static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
665
{
×
666
  vector<ComboAddress> result;
×
667
  result.reserve(items.size());
×
668

669
  for(const auto& item : items) {
×
670
    result.emplace_back(ComboAddress(item.second, port));
×
671
  }
×
672

673
  return result;
×
674
}
×
675

676
/**
677
 * Reads and unify single or multiple sets of ips :
678
 * - {'192.0.2.1', '192.0.2.2'}
679
 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
680
 */
681

682
static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
683
{
×
684
  vector<vector<ComboAddress>> candidates;
×
685

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

699
static vector<string> convStringList(const iplist_t& items)
700
{
×
701
  vector<string> result;
×
702
  result.reserve(items.size());
×
703

704
  for(const auto& item : items) {
×
705
    result.emplace_back(item.second);
×
706
  }
×
707

708
  return result;
×
709
}
×
710

711
static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
712
{
×
713
  vector<pair<int,string> > result;
×
714
  result.reserve(items.size());
×
715

716
  for(const auto& item : items) {
×
717
    result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
×
718
  }
×
719

720
  return result;
×
721
}
×
722

723
bool g_LuaRecordSharedState;
724

725
typedef struct AuthLuaRecordContext
726
{
727
  ComboAddress          bestwho;
728
  DNSName               qname;
729
  DNSZoneRecord         zone_record;
730
  DNSName               zone;
731
} lua_record_ctx_t;
732

733
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
734

735
/*
736
 *  Holds computed hashes for a given entry
737
 */
738
struct EntryHashesHolder
739
{
740
  std::atomic<size_t> weight;
741
  std::string entry;
742
  SharedLockGuarded<std::vector<unsigned int>> hashes;
743
  std::atomic<time_t> lastUsed;
744

745
  EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
×
746
  }
×
747

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

767
using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
768

769
static SharedLockGuarded<std::map<
770
  zone_hashes_key_t, // zoneid qname entry
771
  std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
772
  >>
773
s_zone_hashes;
774

775
static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
776

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

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

806
static std::vector<std::shared_ptr<EntryHashesHolder>> getCHashedEntries(const int zoneId, const std::string& queryName, const std::vector<std::pair<int, std::string>>& items)
807
{
×
808
  std::vector<std::shared_ptr<EntryHashesHolder>> result{};
×
809
  std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
×
810

811
  {
×
812
    time_t now = time(nullptr);
×
813
    auto locked = s_zone_hashes.read_lock();
×
814

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

829
    for (auto& [key, entry]: newEntries) {
×
830
      result.push_back(entry);
×
831
      (*wlocked)[key] = std::move(entry);
×
832
    }
×
833
  }
×
834

835
  return result;
×
836
}
×
837

838
static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
839
{
×
840
  const auto& zoneId = s_lua_record_ctx->zone_record.domain_id;
×
841
  const auto queryName = s_lua_record_ctx->qname.toString();
×
842
  unsigned int sel = std::numeric_limits<unsigned int>::max();
×
843
  unsigned int min = std::numeric_limits<unsigned int>::max();
×
844

845
  boost::optional<std::string> ret;
×
846
  boost::optional<std::string> first;
×
847

848
  cleanZoneHashes();
×
849

850
  auto entries = getCHashedEntries(zoneId, queryName, items);
×
851

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

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

885
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)
886
{
×
887
  vector<vector<ComboAddress> > candidates;
×
888
  opts_t opts;
×
889
  if(options)
×
890
    opts = *options;
×
891

892
  candidates = convMultiComboAddressList(ips, port);
×
893

894
  for(const auto& unit : candidates) {
×
895
    vector<ComboAddress> available;
×
896
    for(const auto& c : unit) {
×
897
      if (upcheckf(c, opts)) {
×
898
        available.push_back(c);
×
899
      }
×
900
    }
×
901
    if(!available.empty()) {
×
902
      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
903
      return convComboAddressListToString(res);
×
904
    }
×
905
  }
×
906

907
  // All units down, apply backupSelector on all candidates
908
  vector<ComboAddress> ret{};
×
909
  for(const auto& unit : candidates) {
×
910
    ret.insert(ret.end(), unit.begin(), unit.end());
×
911
  }
×
912

913
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
×
914
  return convComboAddressListToString(res);
×
915
}
×
916

917
static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cognitive-complexity)
918
{
×
919
  lua.writeFunction("latlon", []() {
×
920
      double lat = 0, lon = 0;
×
921
      getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
×
922
      return std::to_string(lat)+" "+std::to_string(lon);
×
923
    });
×
924
  lua.writeFunction("latlonloc", []() {
×
925
      string loc;
×
926
      getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
×
927
      return loc;
×
928
  });
×
929
  lua.writeFunction("closestMagic", []() {
×
930
      vector<ComboAddress> candidates;
×
931
      // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
932
      for(auto l : s_lua_record_ctx->qname.getRawLabels()) {
×
933
        boost::replace_all(l, "-", ".");
×
934
        try {
×
935
          candidates.emplace_back(l);
×
936
        } catch (const PDNSException& e) {
×
937
          // no need to continue as we most likely reached the end of the ip list
938
          break ;
×
939
        }
×
940
      }
×
941
      return pickclosest(s_lua_record_ctx->bestwho, candidates).toString();
×
942
    });
×
943
  lua.writeFunction("latlonMagic", [](){
×
944
      auto labels= s_lua_record_ctx->qname.getRawLabels();
×
945
      if(labels.size()<4)
×
946
        return std::string("unknown");
×
947
      double lat = 0, lon = 0;
×
948
      getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
×
949
      return std::to_string(lat)+" "+std::to_string(lon);
×
950
    });
×
951

952

953
  lua.writeFunction("createReverse", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
954
      try {
×
955
        auto labels = s_lua_record_ctx->qname.getRawLabels();
×
956
        if(labels.size()<4)
×
957
          return std::string("unknown");
×
958

959
        vector<ComboAddress> candidates;
×
960

961
        // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
962
        // e["1.2.3.4"]="bert.powerdns.com" then provides an exception
963
        if(e) {
×
964
          ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
×
965
          const auto& uom = *e;
×
966
          for(const auto& c : uom)
×
967
            if(ComboAddress(c.first, 0) == req)
×
968
              return c.second;
×
969
        }
×
970
        boost::format fmt(format);
×
971
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
972
        fmt % labels[3] % labels[2] % labels[1] % labels[0];
×
973

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

976
        boost::format fmt2("%02x%02x%02x%02x");
×
977
        for(int i=3; i>=0; --i)
×
978
          fmt2 % atoi(labels[i].c_str());
×
979

980
        fmt % (fmt2.str());
×
981

982
        return fmt.str();
×
983
      }
×
984
      catch(std::exception& ex) {
×
985
        g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
×
986
      }
×
987
      return std::string("error");
×
988
    });
×
989
  lua.writeFunction("createForward", []() {
×
990
      static string allZerosIP{"0.0.0.0"};
×
991
      DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
992
      if (!record_name.isWildcard()) {
×
993
        return allZerosIP;
×
994
      }
×
995
      record_name.chopOff();
×
996
      DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
997

998
      // parts is something like ["1", "2", "3", "4", "static"] or
999
      // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
1000
      auto parts = rel.getRawLabels();
×
1001
      // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
1002
      if(parts.size()>=4) {
×
1003
        try {
×
1004
          ComboAddress ca(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
×
1005
          return ca.toString();
×
1006
        } catch (const PDNSException &e) {
×
1007
          return allZerosIP;
×
1008
        }
×
1009
      } else if (!parts.empty()) {
×
1010
        auto& input = parts.at(0);
×
1011

1012
        // allow a word without - in front, as long as it does not contain anything that could be a number
1013
        size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
×
1014
        if (nonhexprefix > 0) {
×
1015
          input = input.substr(nonhexprefix);
×
1016
        }
×
1017

1018
        // either hex string, or 12-13-14-15
1019
        vector<string> ip_parts;
×
1020

1021
        stringtok(ip_parts, input, "-");
×
1022
        unsigned int x1, x2, x3, x4;
×
1023
        if (ip_parts.size() >= 4) {
×
1024
          // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
1025
          string ret;
×
1026
          for (size_t index=4; index > 0; index--) {
×
1027
            auto octet = ip_parts[ip_parts.size() - index];
×
1028
            try {
×
1029
              auto octetVal = std::stol(octet);
×
1030
              if (octetVal >= 0 && octetVal <= 255) {
×
1031
                ret += ip_parts.at(ip_parts.size() - index) + ".";
×
1032
              } else {
×
1033
                return allZerosIP;
×
1034
              }
×
1035
            } catch (const std::exception &e) {
×
1036
              return allZerosIP;
×
1037
            }
×
1038
          }
×
1039
          ret.resize(ret.size() - 1); // remove trailing dot after last octet
×
1040
          return ret;
×
1041
        }
×
1042
        if(input.length() >= 8) {
×
1043
          auto last8 = input.substr(input.length()-8);
×
1044
          if(sscanf(last8.c_str(), "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
×
1045
            return std::to_string(x1) + "." + std::to_string(x2) + "." + std::to_string(x3) + "." + std::to_string(x4);
×
1046
          }
×
1047
        }
×
1048
      }
×
1049
      return allZerosIP;
×
1050
    });
×
1051

1052
  lua.writeFunction("createForward6", []() {
×
1053
      static string allZerosIP{"::"};
×
1054
      DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
1055
      if (!record_name.isWildcard()) {
×
1056
        return allZerosIP;
×
1057
      }
×
1058
      record_name.chopOff();
×
1059
      DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
1060

1061
      auto parts = rel.getRawLabels();
×
1062
      if(parts.size()==8) {
×
1063
        string tot;
×
1064
        for(int i=0; i<8; ++i) {
×
1065
          if(i)
×
1066
            tot.append(1,':');
×
1067
          tot+=parts[i];
×
1068
        }
×
1069
        ComboAddress ca(tot);
×
1070
        return ca.toString();
×
1071
      }
×
1072
      else if(parts.size()==1) {
×
1073
        if (parts[0].find('-') != std::string::npos) {
×
1074
          boost::replace_all(parts[0],"-",":");
×
1075
          ComboAddress ca(parts[0]);
×
1076
          return ca.toString();
×
1077
        } else {
×
1078
          if (parts[0].size() >= 32) {
×
1079
            auto ippart = parts[0].substr(parts[0].size()-32);
×
1080
            auto fulladdress =
×
1081
              ippart.substr(0, 4) + ":" +
×
1082
              ippart.substr(4, 4) + ":" +
×
1083
              ippart.substr(8, 4) + ":" +
×
1084
              ippart.substr(12, 4) + ":" +
×
1085
              ippart.substr(16, 4) + ":" +
×
1086
              ippart.substr(20, 4) + ":" +
×
1087
              ippart.substr(24, 4) + ":" +
×
1088
              ippart.substr(28, 4);
×
1089

1090
            ComboAddress ca(fulladdress);
×
1091
            return ca.toString();
×
1092
          }
×
1093
        }
×
1094
      }
×
1095

1096
      return allZerosIP;
×
1097
    });
×
1098
  lua.writeFunction("createReverse6", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
1099
      vector<ComboAddress> candidates;
×
1100

1101
      try {
×
1102
        auto labels= s_lua_record_ctx->qname.getRawLabels();
×
1103
        if(labels.size()<32)
×
1104
          return std::string("unknown");
×
1105
        boost::format fmt(format);
×
1106
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
1107

1108

1109
        string together;
×
1110
        vector<string> quads;
×
1111
        for(int i=0; i<8; ++i) {
×
1112
          if(i)
×
1113
            together+=":";
×
1114
          string lquad;
×
1115
          for(int j=0; j <4; ++j) {
×
1116
            lquad.append(1, labels[31-i*4-j][0]);
×
1117
            together += labels[31-i*4-j][0];
×
1118
          }
×
1119
          quads.push_back(lquad);
×
1120
        }
×
1121
        ComboAddress ip6(together,0);
×
1122

1123
        if(e) {
×
1124
          auto& addrs=*e;
×
1125
          for(const auto& addr: addrs) {
×
1126
            // this makes sure we catch all forms of the address
1127
            if(ComboAddress(addr.first,0)==ip6)
×
1128
              return addr.second;
×
1129
          }
×
1130
        }
×
1131

1132
        string dashed=ip6.toString();
×
1133
        boost::replace_all(dashed, ":", "-");
×
1134

1135
        for(int i=31; i>=0; --i)
×
1136
          fmt % labels[i];
×
1137
        fmt % dashed;
×
1138

1139
        for(const auto& lquad : quads)
×
1140
          fmt % lquad;
×
1141

1142
        return fmt.str();
×
1143
      }
×
1144
      catch(std::exception& ex) {
×
1145
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.what()<<endl;
×
1146
      }
×
1147
      catch(PDNSException& ex) {
×
1148
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl;
×
1149
      }
×
1150
      return std::string("unknown");
×
1151
    });
×
1152

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

1156
      if (nmg.match(ComboAddress(address))) {
×
1157
        return {address};
×
1158
      } else {
×
1159
        if (fallback) {
×
1160
          if (fallback->empty()) {
×
1161
            // if fallback is an empty string, return an empty array
1162
            return {};
×
1163
          }
×
1164
          return {*fallback};
×
1165
        }
×
1166

1167
        if (ca.isIPv4()) {
×
1168
          return {string("0.0.0.0")};
×
1169
        } else {
×
1170
          return {string("::")};
×
1171
        }
×
1172
      }
×
1173
    });
×
1174

1175
  /*
1176
   * Simplistic test to see if an IP address listens on a certain port
1177
   * Will return a single IP address from the set of available IP addresses. If
1178
   * no IP address is available, will return a random element of the set of
1179
   * addresses supplied for testing.
1180
   *
1181
   * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
1182
   */
1183
  lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, const boost::optional<std::unordered_map<string,string>> options) {
×
1184
      if (port < 0) {
×
1185
        port = 0;
×
1186
      }
×
1187
      if (port > std::numeric_limits<uint16_t>::max()) {
×
1188
        port = std::numeric_limits<uint16_t>::max();
×
1189
      }
×
1190

1191
      auto checker = [](const ComboAddress& addr, const opts_t& opts) {
×
1192
        return g_up.isUp(addr, opts);
×
1193
      };
×
1194
      return genericIfUp(ips, options, checker, port);
×
1195
    });
×
1196

1197
  lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) {
×
1198
      vector<ComboAddress> candidates;
×
1199
      opts_t opts;
×
1200
      if(options)
×
1201
        opts = *options;
×
1202

1203
      ComboAddress ca_unspec;
×
1204
      ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1205

1206
      // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1207
      for (const auto& [count, unitmap] : ipurls) {
×
1208
        // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1209
        vector<ComboAddress> available;
×
1210

1211
        for (const auto& [ipStr, url] : unitmap) {
×
1212
          // unit: ["192.0.2.1"] = "https://example.com"
1213
          ComboAddress ip(ipStr);
×
1214
          candidates.push_back(ip);
×
1215
          if (g_up.isUp(ca_unspec, url, opts)) {
×
1216
            available.push_back(ip);
×
1217
          }
×
1218
        }
×
1219
        if(!available.empty()) {
×
1220
          vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
1221
          return convComboAddressListToString(res);
×
1222
        }
×
1223
      }
×
1224

1225
      // All units down, apply backupSelector on all candidates
1226
      vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
×
1227
      return convComboAddressListToString(res);
×
1228
    });
×
1229

1230
  lua.writeFunction("ifurlup", [](const std::string& url,
×
1231
                                          const boost::variant<iplist_t, ipunitlist_t>& ips,
×
1232
                                          boost::optional<opts_t> options) {
×
1233

1234
    auto checker = [&url](const ComboAddress& addr, const opts_t& opts) {
×
1235
        return g_up.isUp(addr, url, opts);
×
1236
      };
×
1237
      return genericIfUp(ips, options, checker);
×
1238
    });
×
1239
  /*
1240
   * Returns a random IP address from the supplied list
1241
   * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
1242
   */
1243
  lua.writeFunction("pickrandom", [](const iplist_t& ips) {
×
1244
      vector<string> items = convStringList(ips);
×
1245
      return pickRandom<string>(items);
×
1246
    });
×
1247

1248
  /*
1249
   * Based on the hash of `bestwho`, returns an IP address from the list
1250
   * supplied, weighted according to the results of isUp calls.
1251
   * @example pickselfweighted('http://example.com/weight', { "192.0.2.20", "203.0.113.4", "203.0.113.2" })
1252
   */
1253
  lua.writeFunction("pickselfweighted", [](const std::string& url,
×
1254
                                             const iplist_t& ips,
×
1255
                                             boost::optional<opts_t> options) {
×
1256
      vector< pair<int, ComboAddress> > items;
×
1257
      opts_t opts;
×
1258
      if(options) {
×
1259
        opts = *options;
×
1260
      }
×
1261

1262
      items.reserve(ips.capacity());
×
1263
      bool available = false;
×
1264

1265
      vector<ComboAddress> conv = convComboAddressList(ips);
×
1266
      for (auto& entry : conv) {
×
1267
        int weight = 0;
×
1268
        weight = g_up.isUp(entry, url, opts);
×
1269
        if(weight>0) {
×
1270
          available = true;
×
1271
        }
×
1272
        items.emplace_back(weight, entry);
×
1273
      }
×
1274
      if(available) {
×
1275
        return pickWeightedHashed<ComboAddress>(s_lua_record_ctx->bestwho, items).toString();
×
1276
      }
×
1277

1278
      // All units down, apply backupSelector on all candidates
1279
      return pickWeightedRandom<ComboAddress>(items).toString();
×
1280
    });
×
1281

1282
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
×
1283
      vector<string> items = convStringList(ips);
×
1284
          return pickRandomSample<string>(n, items);
×
1285
    });
×
1286

1287
  lua.writeFunction("pickhashed", [](const iplist_t& ips) {
×
1288
      vector<string> items = convStringList(ips);
×
1289
      return pickHashed<string>(s_lua_record_ctx->bestwho, items);
×
1290
    });
×
1291
  /*
1292
   * Returns a random IP address from the supplied list, as weighted by the
1293
   * various ``weight`` parameters
1294
   * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1295
   */
1296
  lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
×
1297
      vector< pair<int, string> > items = convIntStringPairList(ips);
×
1298
      return pickWeightedRandom<string>(items);
×
1299
    });
×
1300

1301
  /*
1302
   * Based on the hash of `bestwho`, returns an IP address from the list
1303
   * supplied, as weighted by the various `weight` parameters
1304
   * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1305
   */
1306
  lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) {
×
1307
      vector< pair<int, string> > items;
×
1308

1309
      items.reserve(ips.size());
×
1310
      for (auto& entry : ips) {
×
1311
        items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
×
1312
      }
×
1313

1314
      return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1315
    });
×
1316

1317
  /*
1318
   * Based on the hash of the record name, return an IP address from the list
1319
   * supplied, as weighted by the various `weight` parameters
1320
   * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1321
   */
1322
  lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t > ips) {
×
1323
      vector< pair<int, string> > items;
×
1324

1325
      items.reserve(ips.size());
×
1326
      for(auto& i : ips)
×
1327
      {
×
1328
        items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
×
1329
      }
×
1330

1331
      return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
×
1332
    });
×
1333
  /*
1334
   * Based on the hash of `bestwho`, returns an IP address from the list
1335
   * supplied, as weighted by the various `weight` parameters and distributed consistently
1336
   * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1337
   */
1338
  lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) {
×
1339
    std::vector<std::pair<int, std::string>> items;
×
1340

1341
    items.reserve(ips.size());
×
1342
    for (const auto& entry : ips) {
×
1343
      items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
×
1344
    }
×
1345

1346
    return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1347
  });
×
1348

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

1352
      return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1353

1354
    });
×
1355

1356
  if (g_luaRecordExecLimit > 0) {
×
1357
      lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1358
  }
×
1359

1360
  lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){
×
1361
      throw std::runtime_error("Script took too long");
×
1362
    });
×
1363

1364
  lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
×
1365
    return getGeo(ip, attr);
×
1366
  });
×
1367

1368
  typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
×
1369

1370
  lua.writeFunction("asnum", [](const combovar_t& asns) {
×
1371
      string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
×
1372
      return doCompare(asns, res, [](const std::string& a, const std::string& b) {
×
1373
          return !strcasecmp(a.c_str(), b.c_str());
×
1374
        });
×
1375
    });
×
1376
  lua.writeFunction("continent", [](const combovar_t& continent) {
×
1377
     string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1378
      return doCompare(continent, res, [](const std::string& a, const std::string& b) {
×
1379
          return !strcasecmp(a.c_str(), b.c_str());
×
1380
        });
×
1381
    });
×
1382
  lua.writeFunction("continentCode", []() {
×
1383
      string unknown("unknown");
×
1384
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1385
      if ( res == unknown ) {
×
1386
       return std::string("--");
×
1387
      }
×
1388
      return res;
×
1389
    });
×
1390
  lua.writeFunction("country", [](const combovar_t& var) {
×
1391
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1392
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1393
          return !strcasecmp(a.c_str(), b.c_str());
×
1394
        });
×
1395

1396
    });
×
1397
  lua.writeFunction("countryCode", []() {
×
1398
      string unknown("unknown");
×
1399
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1400
      if ( res == unknown ) {
×
1401
       return std::string("--");
×
1402
      }
×
1403
      return res;
×
1404
    });
×
1405
  lua.writeFunction("region", [](const combovar_t& var) {
×
1406
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1407
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1408
          return !strcasecmp(a.c_str(), b.c_str());
×
1409
        });
×
1410

1411
    });
×
1412
  lua.writeFunction("regionCode", []() {
×
1413
      string unknown("unknown");
×
1414
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1415
      if ( res == unknown ) {
×
1416
       return std::string("--");
×
1417
      }
×
1418
      return res;
×
1419
    });
×
1420
  lua.writeFunction("netmask", [](const iplist_t& ips) {
×
1421
      for(const auto& i :ips) {
×
1422
        Netmask nm(i.second);
×
1423
        if(nm.match(s_lua_record_ctx->bestwho))
×
1424
          return true;
×
1425
      }
×
1426
      return false;
×
1427
    });
×
1428
  /* {
1429
       {
1430
        {'192.168.0.0/16', '10.0.0.0/8'},
1431
        {'192.168.20.20', '192.168.20.21'}
1432
       },
1433
       {
1434
        {'0.0.0.0/0'}, {'192.0.2.1'}
1435
       }
1436
     }
1437
  */
1438
  lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) {
×
1439
      for(const auto& rule : in) {
×
1440
        const auto& netmasks=rule.second[0].second;
×
1441
        const auto& destinations=rule.second[1].second;
×
1442
        for(const auto& nmpair : netmasks) {
×
1443
          Netmask nm(nmpair.second);
×
1444
          if(nm.match(s_lua_record_ctx->bestwho)) {
×
1445
            if (destinations.empty()) {
×
1446
              throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm.toString() + ")");
×
1447
            }
×
1448
            return destinations[dns_random(destinations.size())].second;
×
1449
          }
×
1450
        }
×
1451
      }
×
1452
      return std::string();
×
1453
    });
×
1454

1455
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
×
1456
      vector<string> result;
×
1457
          result.reserve(ips.size());
×
1458

1459
      for(const auto& ip : ips) {
×
1460
          result.emplace_back(ip.second);
×
1461
      }
×
1462
      if(result.empty()) {
×
1463
        throw std::invalid_argument("The IP list cannot be empty");
×
1464
      }
×
1465
      return result;
×
1466
    });
×
1467

1468
  lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) {
×
1469
    DNSName rec;
×
1470
    vector<string> ret;
×
1471
    try {
×
1472
      rec = DNSName(record);
×
1473
    }
×
1474
    catch (const std::exception& e) {
×
1475
      g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
×
1476
      return ret;
×
1477
    }
×
1478
    try {
×
1479
      SOAData soaData;
×
1480

1481
      if (!getAuth(rec, qtype, &soaData)) {
×
1482
        return ret;
×
1483
      }
×
1484

1485
      vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id);
×
1486
      for (const auto& drec : drs) {
×
1487
        ret.push_back(drec.dr.getContent()->getZoneRepresentation());
×
1488
      }
×
1489
    }
×
1490
    catch (std::exception& e) {
×
1491
      g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
×
1492
    }
×
1493
    return ret;
×
1494
  });
×
1495

1496
  lua.writeFunction("include", [&lua](string record) {
×
1497
      DNSName rec;
×
1498
      try {
×
1499
        rec = DNSName(record) + s_lua_record_ctx->zone;
×
1500
      } catch (const std::exception &e){
×
1501
        g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl;
×
1502
        return;
×
1503
      }
×
1504
      try {
×
1505
        vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zone_record.domain_id);
×
1506
        for(const auto& dr : drs) {
×
1507
          auto lr = getRR<LUARecordContent>(dr.dr);
×
1508
          lua.executeCode(lr->getCode());
×
1509
        }
×
1510
      }
×
1511
      catch(std::exception& e) {
×
1512
        g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<rec<<": "<<e.what()<<endl;
×
1513
      }
×
1514
    });
×
1515
}
×
1516

1517
std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSZoneRecord& zone_record, const DNSName& zone, const DNSPacket& dnsp, uint16_t qtype, unique_ptr<AuthLua4>& LUA)
1518
{
×
1519
  if(!LUA ||                  // we don't have a Lua state yet
×
1520
     !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1521
    LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
×
1522
    setupLuaRecords(*LUA->getLua());
×
1523
  }
×
1524

1525
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1526

1527
  LuaContext& lua = *LUA->getLua();
×
1528

1529
  s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1530
  s_lua_record_ctx->qname = query;
×
1531
  s_lua_record_ctx->zone_record = zone_record;
×
1532
  s_lua_record_ctx->zone = zone;
×
1533

1534
  lua.writeVariable("qname", query);
×
1535
  lua.writeVariable("zone", zone);
×
1536
  lua.writeVariable("zoneid", zone_record.domain_id);
×
1537
  lua.writeVariable("who", dnsp.getInnerRemote());
×
1538
  lua.writeVariable("localwho", dnsp.getLocal());
×
1539
  lua.writeVariable("dh", (dnsheader*)&dnsp.d);
×
1540
  lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
×
1541
  lua.writeVariable("tcp", dnsp.d_tcp);
×
1542
  lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
×
1543
  if(dnsp.hasEDNSSubnet()) {
×
1544
    lua.writeVariable("ecswho", dnsp.getRealRemote());
×
1545
    s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
×
1546
  }
×
1547
  else {
×
1548
    lua.writeVariable("ecswho", nullptr);
×
1549
    s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
×
1550
  }
×
1551
  lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
×
1552

1553
  try {
×
1554
    string actual;
×
1555
    if(!code.empty() && code[0]!=';')
×
1556
      actual = "return " + code;
×
1557
    else
×
1558
      actual = code.substr(1);
×
1559

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

1562
    vector<string> contents;
×
1563
    if(auto str = boost::get<string>(&content))
×
1564
      contents.push_back(*str);
×
1565
    else
×
1566
      for(const auto& c : boost::get<vector<pair<int,string>>>(content))
×
1567
        contents.push_back(c.second);
×
1568

1569
    for(const auto& content_it: contents) {
×
1570
      if(qtype==QType::TXT)
×
1571
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
×
1572
      else
×
1573
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
×
1574
    }
×
1575
  } catch(std::exception &e) {
×
1576
    g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
×
1577
    try {
×
1578
      std::rethrow_if_nested(e);
×
1579
      g_log<<endl;
×
1580
    } catch(const std::exception& ne) {
×
1581
      g_log << ": " << ne.what() << std::endl;
×
1582
    }
×
1583
    catch(const PDNSException& ne) {
×
1584
      g_log << ": " << ne.reason << std::endl;
×
1585
    }
×
1586
    throw ;
×
1587
  }
×
1588

1589
  return ret;
×
1590
}
×
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