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

PowerDNS / pdns / 12452944705

22 Dec 2024 09:10AM UTC coverage: 64.791% (+0.2%) from 64.568%
12452944705

Pull #10692

github

web-flow
Merge 261b10c6f into d165e0b05
Pull Request #10692: auth: added self weighted lua function

37714 of 89004 branches covered (42.37%)

Branch coverage included in aggregate %.

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

11285 existing lines in 160 files now uncovered.

126084 of 163806 relevant lines covered (76.97%)

4314414.81 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
   unify ifurlup/ifportup
33
      add attribute for certificate check
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
    /* last time the status was accessed */
79
    std::atomic<time_t> lastAccess{0};
80
  };
81

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

92
private:
93
  void checkURL(const CheckDesc& cd, const bool status, const bool first = false)
94
  {
×
95
    setThreadName("pdns/lua-c-url");
×
96

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

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

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

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

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

201
          if (desc.url.empty()) { // TCP
×
202
            results.push_back(std::async(std::launch::async, &IsUpOracle::checkTCP, this, desc, state->status.load(), state->first.load()));
×
203
          } else { // URL
×
204
            results.push_back(std::async(std::launch::async, &IsUpOracle::checkURL, this, desc, state->status.load(), state->first.load()));
×
205
          }
×
206
          if (std::chrono::system_clock::from_time_t(state->lastAccess) < (checkStart - std::chrono::seconds(g_luaHealthChecksExpireDelay))) {
×
UNCOV
207
            toDelete.push_back(desc);
×
208
          }
×
209
        }
×
210
      }
×
211
      // we can release the lock as nothing will be deleted
212
      for (auto& future: results) {
×
213
        future.wait();
×
214
      }
×
215
      if (!toDelete.empty()) {
×
216
        auto statuses = d_statuses.write_lock();
×
217
        for (auto& it: toDelete) {
×
218
          statuses->erase(it);
×
219
        }
×
UNCOV
220
      }
×
221

222
      // set thread name again, in case std::async surprised us by doing work in this thread
UNCOV
223
      setThreadName("pdns/luaupcheck");
×
224

UNCOV
225
      std::this_thread::sleep_until(checkStart + std::chrono::seconds(g_luaHealthChecksInterval));
×
UNCOV
226
    }
×
UNCOV
227
  }
×
228

229
  typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
230
  SharedLockGuarded<statuses_t> d_statuses;
231

232
  std::unique_ptr<std::thread> d_checkerThread;
233
  std::atomic_flag d_checkerThreadStarted;
234

235
  void setStatus(const CheckDesc& cd, bool status)
UNCOV
236
  {
×
NEW
237
    auto statuses = d_statuses.write_lock();
×
NEW
238
    auto& state = (*statuses)[cd];
×
NEW
239
    state->status = status;
×
NEW
240
    if (state->first) {
×
NEW
241
      state->first = false;
×
NEW
242
    }
×
243
  }
×
244

245
  void setWeight(const CheckDesc& cd, int weight){
×
246
    auto statuses = d_statuses.write_lock();
×
247
    auto& state = (*statuses)[cd];
×
UNCOV
248
    state->weight = weight;
×
UNCOV
249
  }
×
250

251
  void setDown(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
252
  {
×
253
    CheckDesc cd{rem, url, opts};
×
254
    setStatus(cd, false);
×
UNCOV
255
  }
×
256

257
  void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
258
  {
×
259
    CheckDesc cd{rem, url, opts};
×
UNCOV
260

×
UNCOV
261
    setStatus(cd, true);
×
262
  }
×
263

264
  void setDown(const CheckDesc& cd)
UNCOV
265
  {
×
UNCOV
266
    setStatus(cd, false);
×
NEW
267
  }
×
268

269
  void setUp(const CheckDesc& cd)
270
  {
×
271
    setStatus(cd, true);
×
272
  }
×
273
};
274

275
int IsUpOracle::isUp(const CheckDesc& cd)
276
{
×
277
  if (!d_checkerThreadStarted.test_and_set()) {
×
NEW
278
    d_checkerThread = std::make_unique<std::thread>([this] { return checkThread(); });
×
NEW
279
  }
×
NEW
280
  time_t now = time(nullptr);
×
281
  {
×
282
    auto statuses = d_statuses.read_lock();
×
283
    auto iter = statuses->find(cd);
×
UNCOV
284
    if (iter != statuses->end()) {
×
285
      iter->second->lastAccess = now;
×
286
      if (iter->second->weight > 0) {
×
287
        return iter->second->weight;
×
288
      }
×
289
      return iter->second->status;
×
UNCOV
290
    }
×
291
  }
×
292
  // try to parse options so we don't insert any malformed content
293
  if (cd.opts.count("source")) {
×
294
    ComboAddress src(cd.opts.at("source"));
×
295
  }
×
296
  {
×
UNCOV
297
    auto statuses = d_statuses.write_lock();
×
298
    // Make sure we don't insert new entry twice now we have the lock
299
    if (statuses->find(cd) == statuses->end()) {
×
300
      (*statuses)[cd] = std::make_unique<CheckState>(now);
×
301
    }
×
302
  }
×
UNCOV
303
  return false;
×
NEW
304
}
×
305

306
int IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts)
307
{
×
308
  CheckDesc cd{remote, "", opts};
×
UNCOV
309
  return isUp(cd);
×
UNCOV
310
}
×
311

312
int IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts)
UNCOV
313
{
×
314
  CheckDesc cd{remote, url, opts};
×
315
  return isUp(cd);
×
316
}
×
317

318
IsUpOracle g_up;
319
namespace {
320
template<typename T, typename C>
321
bool doCompare(const T& var, const std::string& res, const C& cmp)
322
{
×
323
  if(auto country = boost::get<string>(&var))
×
324
    return cmp(*country, res);
×
325

UNCOV
326
  auto countries=boost::get<vector<pair<int,string> > >(&var);
×
UNCOV
327
  for(const auto& country : *countries) {
×
328
    if(cmp(country.second, res))
×
329
      return true;
×
330
  }
×
331
  return false;
×
332
}
×
333
}
334

335
static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
336
{
×
337
  static bool initialized;
×
338
  extern std::function<std::string(const std::string& ip, int)> g_getGeo;
×
339
  if(!g_getGeo) {
×
340
    if(!initialized) {
×
UNCOV
341
      g_log<<Logger::Error<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl;
×
UNCOV
342
      initialized=true;
×
UNCOV
343
    }
×
344
    return "unknown";
×
345
  }
×
346
  else
×
347
    return g_getGeo(ip, (int)qa);
×
348
}
×
349

350
template <typename T>
351
static T pickRandom(const vector<T>& items)
UNCOV
352
{
×
353
  if (items.empty()) {
×
354
    throw std::invalid_argument("The items list cannot be empty");
×
355
  }
×
356
  return items[dns_random(items.size())];
×
357
}
×
358

359
template <typename T>
360
static T pickHashed(const ComboAddress& who, const vector<T>& items)
UNCOV
361
{
×
UNCOV
362
  if (items.empty()) {
×
363
    throw std::invalid_argument("The items list cannot be empty");
×
364
  }
×
365
  ComboAddress::addressOnlyHash aoh;
×
366
  return items[aoh(who) % items.size()];
×
367
}
×
368

369
template <typename T>
370
static T pickWeightedRandom(const vector< pair<int, T> >& items)
371
{
×
372
  if (items.empty()) {
×
373
    throw std::invalid_argument("The items list cannot be empty");
×
374
  }
×
UNCOV
375
  int sum=0;
×
376
  vector< pair<int, T> > pick;
×
377
  pick.reserve(items.size());
×
378

UNCOV
379
  for(auto& i : items) {
×
380
    sum += i.first;
×
381
    pick.emplace_back(sum, i.second);
×
382
  }
×
383

UNCOV
384
  if (sum == 0) {
×
UNCOV
385
    throw std::invalid_argument("The sum of items cannot be zero");
×
UNCOV
386
  }
×
387

388
  int r = dns_random(sum);
×
389
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
390
  return p->second;
×
391
}
×
392

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

UNCOV
403
  for(auto& i : items) {
×
404
    sum += i.first;
×
405
    pick.push_back({sum, i.second});
×
406
  }
×
407

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

412
  ComboAddress::addressOnlyHash aoh;
×
413
  int r = aoh(bestwho) % sum;
×
414
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
415
  return p->second;
×
416
}
×
417

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

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

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

437
  size_t r = dnsname.hash() % 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;
×
UNCOV
440
}
×
441

442
template <typename T>
443
static vector<T> pickRandomSample(int n, const vector<T>& items)
444
{
×
445
  if (items.empty()) {
×
446
    throw std::invalid_argument("The items list cannot be empty");
×
UNCOV
447
  }
×
448

UNCOV
449
  vector<T> pick;
×
450
  pick.reserve(items.size());
×
451

452
  for(auto& item : items) {
×
UNCOV
453
    pick.push_back(item);
×
454
  }
×
455

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

458
  if (count == 0) {
×
UNCOV
459
    return vector<T>();
×
UNCOV
460
  }
×
461

462
  std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
×
463

464
  vector<T> result = {pick.begin(), pick.begin() + count};
×
465
  return result;
×
466
}
×
467

468
static bool getLatLon(const std::string& ip, double& lat, double& lon)
469
{
×
470
  string inp = getGeo(ip, GeoIPInterface::Location);
×
UNCOV
471
  if(inp.empty())
×
UNCOV
472
    return false;
×
473
  lat=atof(inp.c_str());
×
474
  auto pos=inp.find(' ');
×
475
  if(pos != string::npos)
×
476
    lon=atof(inp.c_str() + pos);
×
UNCOV
477
  return true;
×
478
}
×
479

480
static bool getLatLon(const std::string& ip, string& loc)
UNCOV
481
{
×
482
  int latdeg, latmin, londeg, lonmin;
×
483
  double latsec, lonsec;
×
484
  char lathem='X', lonhem='X';
×
485

486
  double lat = 0, lon = 0;
×
487
  if(!getLatLon(ip, lat, lon))
×
488
    return false;
×
489

490
  if(lat > 0) {
×
491
    lathem='N';
×
492
  }
×
493
  else {
×
494
    lat = -lat;
×
495
    lathem='S';
×
496
  }
×
497

498
  if(lon > 0) {
×
499
    lonhem='E';
×
500
  }
×
UNCOV
501
  else {
×
502
    lon = -lon;
×
503
    lonhem='W';
×
504
  }
×
505

UNCOV
506
  latdeg = lat;
×
UNCOV
507
  latmin = (lat - latdeg)*60.0;
×
508
  latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
×
509

510
  londeg = lon;
×
511
  lonmin = (lon - londeg)*60.0;
×
512
  lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
×
513

514
  // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
515

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

518
  loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
×
519
  return true;
×
520
}
×
521

522
static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips)
523
{
×
524
  if (wips.empty()) {
×
525
    throw std::invalid_argument("The IP list cannot be empty");
×
526
  }
×
UNCOV
527
  map<double, vector<ComboAddress> > ranked;
×
528
  double wlat=0, wlon=0;
×
529
  getLatLon(bestwho.toString(), wlat, wlon);
×
530
  //        cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
531
  vector<string> ret;
×
532
  for(const auto& c : wips) {
×
UNCOV
533
    double lat=0, lon=0;
×
534
    getLatLon(c.toString(), lat, lon);
×
535
    //          cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
536
    double latdiff = wlat-lat;
×
537
    double londiff = wlon-lon;
×
UNCOV
538
    if(londiff > 180)
×
UNCOV
539
      londiff = 360 - londiff;
×
540
    double dist2=latdiff*latdiff + londiff*londiff;
×
541
    //          cout<<"    distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
UNCOV
542
    ranked[dist2].push_back(c);
×
543
  }
×
544
  return ranked.begin()->second[dns_random(ranked.begin()->second.size())];
×
545
}
×
546

547
static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid)
548
{
×
549
  static LockGuarded<UeberBackend> s_ub;
×
550

551
  DNSZoneRecord dr;
×
552
  vector<DNSZoneRecord> ret;
×
553
  {
×
UNCOV
554
    auto ub = s_ub.lock();
×
UNCOV
555
    ub->lookup(QType(qtype), name, zoneid);
×
556
    while (ub->get(dr)) {
×
557
      ret.push_back(dr);
×
UNCOV
558
    }
×
559
  }
×
560
  return ret;
×
561
}
×
562

563
static bool getAuth(const DNSName& name, uint16_t qtype, SOAData* soaData)
UNCOV
564
{
×
UNCOV
565
  static LockGuarded<UeberBackend> s_ub;
×
566

567
  {
×
568
    auto ueback = s_ub.lock();
×
569
    return ueback->getAuth(name, qtype, soaData);
×
570
  }
×
571
}
×
572

573
static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
UNCOV
574
{
×
UNCOV
575
  string selector=defaultValue;
×
576
  if(options) {
×
577
    if(options->count(name))
×
UNCOV
578
      selector=options->find(name)->second;
×
579
  }
×
580
  return selector;
×
581
}
×
582

583
static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
584
{
×
585
  vector<ComboAddress> ret;
×
586

587
  if(selector=="all")
×
588
    return candidates;
×
589
  else if(selector=="random")
×
590
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
UNCOV
591
  else if(selector=="pickclosest")
×
592
    ret.emplace_back(pickclosest(bestwho, candidates));
×
593
  else if(selector=="hashed")
×
UNCOV
594
    ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates));
×
UNCOV
595
  else {
×
596
    g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl;
×
597
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
598
  }
×
599

600
  return ret;
×
601
}
×
602

603
static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
604
{
×
605
  vector<string> result;
×
UNCOV
606
  result.reserve(items.size());
×
607

608
  for (const auto& item : items) {
×
609
    result.emplace_back(item.toString());
×
610
  }
×
611

612
  return result;
×
613
}
×
614

615
static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
616
{
×
617
  vector<ComboAddress> result;
×
UNCOV
618
  result.reserve(items.size());
×
619

UNCOV
620
  for(const auto& item : items) {
×
UNCOV
621
    result.emplace_back(ComboAddress(item.second, port));
×
UNCOV
622
  }
×
623

UNCOV
624
  return result;
×
UNCOV
625
}
×
626

627
/**
628
 * Reads and unify single or multiple sets of ips :
629
 * - {'192.0.2.1', '192.0.2.2'}
630
 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
631
 */
632

633
static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
634
{
×
635
  vector<vector<ComboAddress>> candidates;
×
636

637
  if(auto simple = boost::get<iplist_t>(&items)) {
×
638
    vector<ComboAddress> unit = convComboAddressList(*simple, port);
×
639
    candidates.push_back(unit);
×
640
  } else {
×
UNCOV
641
    auto units = boost::get<ipunitlist_t>(items);
×
UNCOV
642
    for(const auto& u : units) {
×
643
      vector<ComboAddress> unit = convComboAddressList(u.second, port);
×
644
      candidates.push_back(unit);
×
645
    }
×
UNCOV
646
  }
×
647
  return candidates;
×
648
}
×
649

650
static vector<string> convStringList(const iplist_t& items)
651
{
×
652
  vector<string> result;
×
UNCOV
653
  result.reserve(items.size());
×
654

655
  for(const auto& item : items) {
×
656
    result.emplace_back(item.second);
×
657
  }
×
658

659
  return result;
×
660
}
×
661

662
static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
663
{
×
664
  vector<pair<int,string> > result;
×
UNCOV
665
  result.reserve(items.size());
×
666

UNCOV
667
  for(const auto& item : items) {
×
UNCOV
668
    result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
×
UNCOV
669
  }
×
670

UNCOV
671
  return result;
×
UNCOV
672
}
×
673

674
bool g_LuaRecordSharedState;
675

676
typedef struct AuthLuaRecordContext
677
{
678
  ComboAddress          bestwho;
679
  DNSName               qname;
680
  DNSName               zone;
681
  int                   zoneid;
682
} lua_record_ctx_t;
683

684
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
685

686
/*
687
 *  Holds computed hashes for a given entry
688
 */
689
struct EntryHashesHolder
690
{
691
  std::atomic<size_t> weight;
692
  std::string entry;
693
  SharedLockGuarded<std::vector<unsigned int>> hashes;
694
  std::atomic<time_t> lastUsed;
695

696
  EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
×
697
  }
×
698

699
  bool hashesComputed() {
×
700
    return weight == hashes.read_lock()->size();
×
UNCOV
701
  }
×
702
  void hash() {
×
703
    auto locked = hashes.write_lock();
×
704
    locked->clear();
×
705
    locked->reserve(weight);
×
706
    size_t count = 0;
×
707
    while (count < weight) {
×
UNCOV
708
      auto value = boost::str(boost::format("%s-%d") % entry % count);
×
709
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
UNCOV
710
      auto whash = burtle(reinterpret_cast<const unsigned char*>(value.data()), value.size(), 0);
×
UNCOV
711
      locked->push_back(whash);
×
UNCOV
712
      ++count;
×
UNCOV
713
    }
×
UNCOV
714
    std::sort(locked->begin(), locked->end());
×
UNCOV
715
  }
×
716
};
717

718
using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
719

720
static SharedLockGuarded<std::map<
721
  zone_hashes_key_t, // zoneid qname entry
722
  std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
723
  >>
724
s_zone_hashes;
725

726
static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
727

728
/**
729
 * every ~g_luaConsistentHashesCleanupInterval, do a cleanup to delete entries that haven't been used in the last g_luaConsistentHashesExpireDelay
730
 */
731
static void cleanZoneHashes()
732
{
×
733
  auto now = time(nullptr);
×
UNCOV
734
  if (s_lastConsistentHashesCleanup > (now - g_luaConsistentHashesCleanupInterval)) {
×
735
    return ;
×
736
  }
×
737
  s_lastConsistentHashesCleanup = now;
×
738
  std::vector<zone_hashes_key_t> toDelete{};
×
739
  {
×
740
    auto locked = s_zone_hashes.read_lock();
×
741
    auto someTimeAgo = now - g_luaConsistentHashesExpireDelay;
×
742

743
    for (const auto& [key, entry]: *locked) {
×
744
      if (entry->lastUsed > someTimeAgo) {
×
745
        toDelete.push_back(key);
×
746
      }
×
747
    }
×
UNCOV
748
  }
×
UNCOV
749
  if (!toDelete.empty()) {
×
750
    auto wlocked = s_zone_hashes.write_lock();
×
751
    for (const auto& key : toDelete) {
×
752
      wlocked->erase(key);
×
UNCOV
753
    }
×
754
  }
×
755
}
×
756

757
static std::vector<std::shared_ptr<EntryHashesHolder>> getCHashedEntries(const int zoneId, const std::string& queryName, const std::vector<std::pair<int, std::string>>& items)
758
{
×
759
  std::vector<std::shared_ptr<EntryHashesHolder>> result{};
×
760
  std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
×
761

762
  {
×
763
    time_t now = time(nullptr);
×
764
    auto locked = s_zone_hashes.read_lock();
×
765

766
    for (const auto& [weight, entry]: items) {
×
767
      auto key = std::make_tuple(zoneId, queryName, entry);
×
768
      if (locked->count(key) == 0) {
×
769
        newEntries[key] = std::make_shared<EntryHashesHolder>(weight, entry, now);
×
770
      } else {
×
UNCOV
771
        locked->at(key)->weight = weight;
×
772
        locked->at(key)->lastUsed = now;
×
773
        result.push_back(locked->at(key));
×
774
      }
×
775
    }
×
776
  }
×
UNCOV
777
  if (!newEntries.empty()) {
×
778
    auto wlocked = s_zone_hashes.write_lock();
×
779

UNCOV
780
    for (auto& [key, entry]: newEntries) {
×
UNCOV
781
      result.push_back(entry);
×
782
      (*wlocked)[key] = std::move(entry);
×
783
    }
×
784
  }
×
785

786
  return result;
×
UNCOV
787
}
×
788

789
static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
UNCOV
790
{
×
791
  const auto& zoneId = s_lua_record_ctx->zoneid;
×
UNCOV
792
  const auto queryName = s_lua_record_ctx->qname.toString();
×
793
  unsigned int sel = std::numeric_limits<unsigned int>::max();
×
UNCOV
794
  unsigned int min = std::numeric_limits<unsigned int>::max();
×
795

796
  boost::optional<std::string> ret;
×
797
  boost::optional<std::string> first;
×
798

799
  cleanZoneHashes();
×
800

801
  auto entries = getCHashedEntries(zoneId, queryName, items);
×
802

803
  ComboAddress::addressOnlyHash addrOnlyHash;
×
804
  auto qhash = addrOnlyHash(bestwho);
×
805
  for (const auto& entry : entries) {
×
806
    if (!entry->hashesComputed()) {
×
807
      entry->hash();
×
UNCOV
808
    }
×
809
    {
×
810
      const auto hashes = entry->hashes.read_lock();
×
811
      if (!hashes->empty()) {
×
812
        if (min > *(hashes->begin())) {
×
813
          min = *(hashes->begin());
×
814
          first = entry->entry;
×
815
        }
×
816

817
        auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
×
818
        if (hash_it != hashes->end()) {
×
819
          if (*hash_it < sel) {
×
820
            sel = *hash_it;
×
821
            ret = entry->entry;
×
822
          }
×
823
        }
×
824
      }
×
825
    }
×
826
  }
×
UNCOV
827
  if (ret != boost::none) {
×
UNCOV
828
    return *ret;
×
829
  }
×
830
  if (first != boost::none) {
×
831
    return *first;
×
832
  }
×
833
  return {};
×
UNCOV
834
}
×
835

836
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)
837
{
×
838
  vector<vector<ComboAddress> > candidates;
×
839
  opts_t opts;
×
840
  if(options)
×
841
    opts = *options;
×
842

843
  candidates = convMultiComboAddressList(ips, port);
×
844

845
  for(const auto& unit : candidates) {
×
846
    vector<ComboAddress> available;
×
847
    for(const auto& c : unit) {
×
848
      if (upcheckf(c, opts)) {
×
UNCOV
849
        available.push_back(c);
×
UNCOV
850
      }
×
851
    }
×
852
    if(!available.empty()) {
×
853
      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
854
      return convComboAddressListToString(res);
×
UNCOV
855
    }
×
856
  }
×
857

858
  // All units down, apply backupSelector on all candidates
UNCOV
859
  vector<ComboAddress> ret{};
×
UNCOV
860
  for(const auto& unit : candidates) {
×
861
    ret.insert(ret.end(), unit.begin(), unit.end());
×
862
  }
×
863

864
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
×
865
  return convComboAddressListToString(res);
×
866
}
×
867

868
static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cognitive-complexity)
869
{
×
870
  lua.writeFunction("latlon", []() {
×
871
      double lat = 0, lon = 0;
×
872
      getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
×
873
      return std::to_string(lat)+" "+std::to_string(lon);
×
UNCOV
874
    });
×
875
  lua.writeFunction("latlonloc", []() {
×
876
      string loc;
×
877
      getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
×
878
      return loc;
×
879
  });
×
UNCOV
880
  lua.writeFunction("closestMagic", []() {
×
881
      vector<ComboAddress> candidates;
×
882
      // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
883
      for(auto l : s_lua_record_ctx->qname.getRawLabels()) {
×
884
        boost::replace_all(l, "-", ".");
×
885
        try {
×
886
          candidates.emplace_back(l);
×
887
        } catch (const PDNSException& e) {
×
888
          // no need to continue as we most likely reached the end of the ip list
889
          break ;
×
890
        }
×
891
      }
×
892
      return pickclosest(s_lua_record_ctx->bestwho, candidates).toString();
×
893
    });
×
UNCOV
894
  lua.writeFunction("latlonMagic", [](){
×
UNCOV
895
      auto labels= s_lua_record_ctx->qname.getRawLabels();
×
896
      if(labels.size()<4)
×
897
        return std::string("unknown");
×
898
      double lat = 0, lon = 0;
×
899
      getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
×
900
      return std::to_string(lat)+" "+std::to_string(lon);
×
UNCOV
901
    });
×
902

903

UNCOV
904
  lua.writeFunction("createReverse", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
UNCOV
905
      try {
×
906
        auto labels = s_lua_record_ctx->qname.getRawLabels();
×
907
        if(labels.size()<4)
×
908
          return std::string("unknown");
×
909

910
        vector<ComboAddress> candidates;
×
911

912
        // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
913
        // e["1.2.3.4"]="bert.powerdns.com" then provides an exception
914
        if(e) {
×
915
          ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
×
UNCOV
916
          const auto& uom = *e;
×
917
          for(const auto& c : uom)
×
UNCOV
918
            if(ComboAddress(c.first, 0) == req)
×
919
              return c.second;
×
920
        }
×
921
        boost::format fmt(format);
×
UNCOV
922
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
923
        fmt % labels[3] % labels[2] % labels[1] % labels[0];
×
924

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

927
        boost::format fmt2("%02x%02x%02x%02x");
×
928
        for(int i=3; i>=0; --i)
×
929
          fmt2 % atoi(labels[i].c_str());
×
930

931
        fmt % (fmt2.str());
×
932

933
        return fmt.str();
×
934
      }
×
UNCOV
935
      catch(std::exception& ex) {
×
UNCOV
936
        g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
×
937
      }
×
UNCOV
938
      return std::string("error");
×
939
    });
×
940
  lua.writeFunction("createForward", []() {
×
941
      static string allZerosIP("0.0.0.0");
×
942
      DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
×
943
      // parts is something like ["1", "2", "3", "4", "static"] or
944
      // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
945
      auto parts = rel.getRawLabels();
×
946
      // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
947
      if(parts.size()>=4) {
×
UNCOV
948
        try {
×
UNCOV
949
          ComboAddress ca(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
×
950
          return ca.toString();
×
951
        } catch (const PDNSException &e) {
×
952
          return allZerosIP;
×
953
        }
×
UNCOV
954
      } else if (!parts.empty()) {
×
UNCOV
955
        auto& input = parts.at(0);
×
956

957
        // allow a word without - in front, as long as it does not contain anything that could be a number
958
        size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
×
959
        if (nonhexprefix > 0) {
×
960
          input = input.substr(nonhexprefix);
×
UNCOV
961
        }
×
962

963
        // either hex string, or 12-13-14-15
964
        vector<string> ip_parts;
×
965

966
        stringtok(ip_parts, input, "-");
×
967
        unsigned int x1, x2, x3, x4;
×
968
        if (ip_parts.size() >= 4) {
×
969
          // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
970
          string ret;
×
971
          for (size_t index=4; index > 0; index--) {
×
972
            auto octet = ip_parts[ip_parts.size() - index];
×
973
            try {
×
974
              auto octetVal = std::stol(octet);
×
975
              if (octetVal >= 0 && octetVal <= 255) {
×
976
                ret += ip_parts.at(ip_parts.size() - index) + ".";
×
977
              } else {
×
978
                return allZerosIP;
×
979
              }
×
980
            } catch (const std::exception &e) {
×
981
              return allZerosIP;
×
982
            }
×
983
          }
×
984
          ret.resize(ret.size() - 1); // remove trailing dot after last octet
×
985
          return ret;
×
986
        }
×
987
        if(input.length() >= 8) {
×
UNCOV
988
          auto last8 = input.substr(input.length()-8);
×
989
          if(sscanf(last8.c_str(), "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
×
990
            return std::to_string(x1) + "." + std::to_string(x2) + "." + std::to_string(x3) + "." + std::to_string(x4);
×
991
          }
×
992
        }
×
993
      }
×
994
      return allZerosIP;
×
995
    });
×
996

997
  lua.writeFunction("createForward6", []() {
×
998
      DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
×
999
      auto parts = rel.getRawLabels();
×
1000
      if(parts.size()==8) {
×
1001
        string tot;
×
1002
        for(int i=0; i<8; ++i) {
×
1003
          if(i)
×
1004
            tot.append(1,':');
×
1005
          tot+=parts[i];
×
1006
        }
×
1007
        ComboAddress ca(tot);
×
1008
        return ca.toString();
×
1009
      }
×
1010
      else if(parts.size()==1) {
×
1011
        if (parts[0].find('-') != std::string::npos) {
×
1012
          boost::replace_all(parts[0],"-",":");
×
1013
          ComboAddress ca(parts[0]);
×
1014
          return ca.toString();
×
1015
        } else {
×
1016
          if (parts[0].size() >= 32) {
×
1017
            auto ippart = parts[0].substr(parts[0].size()-32);
×
1018
            auto fulladdress =
×
UNCOV
1019
              ippart.substr(0, 4) + ":" +
×
1020
              ippart.substr(4, 4) + ":" +
×
1021
              ippart.substr(8, 4) + ":" +
×
1022
              ippart.substr(12, 4) + ":" +
×
1023
              ippart.substr(16, 4) + ":" +
×
1024
              ippart.substr(20, 4) + ":" +
×
UNCOV
1025
              ippart.substr(24, 4) + ":" +
×
1026
              ippart.substr(28, 4);
×
1027

1028
            ComboAddress ca(fulladdress);
×
1029
            return ca.toString();
×
UNCOV
1030
          }
×
1031
        }
×
1032
      }
×
1033

1034
      return std::string("::");
×
1035
    });
×
1036
  lua.writeFunction("createReverse6", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
UNCOV
1037
      vector<ComboAddress> candidates;
×
1038

1039
      try {
×
1040
        auto labels= s_lua_record_ctx->qname.getRawLabels();
×
1041
        if(labels.size()<32)
×
1042
          return std::string("unknown");
×
1043
        boost::format fmt(format);
×
1044
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
1045

1046

1047
        string together;
×
1048
        vector<string> quads;
×
1049
        for(int i=0; i<8; ++i) {
×
1050
          if(i)
×
1051
            together+=":";
×
UNCOV
1052
          string lquad;
×
1053
          for(int j=0; j <4; ++j) {
×
1054
            lquad.append(1, labels[31-i*4-j][0]);
×
1055
            together += labels[31-i*4-j][0];
×
UNCOV
1056
          }
×
1057
          quads.push_back(lquad);
×
1058
        }
×
1059
        ComboAddress ip6(together,0);
×
1060

UNCOV
1061
        if(e) {
×
1062
          auto& addrs=*e;
×
1063
          for(const auto& addr: addrs) {
×
1064
            // this makes sure we catch all forms of the address
1065
            if(ComboAddress(addr.first,0)==ip6)
×
1066
              return addr.second;
×
1067
          }
×
UNCOV
1068
        }
×
1069

1070
        string dashed=ip6.toString();
×
UNCOV
1071
        boost::replace_all(dashed, ":", "-");
×
1072

1073
        for(int i=31; i>=0; --i)
×
1074
          fmt % labels[i];
×
1075
        fmt % dashed;
×
1076

1077
        for(const auto& lquad : quads)
×
1078
          fmt % lquad;
×
1079

1080
        return fmt.str();
×
1081
      }
×
UNCOV
1082
      catch(std::exception& ex) {
×
1083
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.what()<<endl;
×
1084
      }
×
UNCOV
1085
      catch(PDNSException& ex) {
×
1086
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl;
×
1087
      }
×
1088
      return std::string("unknown");
×
1089
    });
×
1090

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

1094
      if (nmg.match(ComboAddress(address))) {
×
1095
        return {address};
×
UNCOV
1096
      } else {
×
1097
        if (fallback) {
×
1098
          if (fallback->empty()) {
×
1099
            // if fallback is an empty string, return an empty array
1100
            return {};
×
1101
          }
×
1102
          return {*fallback};
×
1103
        }
×
1104

UNCOV
1105
        if (ca.isIPv4()) {
×
UNCOV
1106
          return {string("0.0.0.0")};
×
UNCOV
1107
        } else {
×
UNCOV
1108
          return {string("::")};
×
UNCOV
1109
        }
×
UNCOV
1110
      }
×
UNCOV
1111
    });
×
1112

1113
  /*
1114
   * Simplistic test to see if an IP address listens on a certain port
1115
   * Will return a single IP address from the set of available IP addresses. If
1116
   * no IP address is available, will return a random element of the set of
1117
   * addresses supplied for testing.
1118
   *
1119
   * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
1120
   */
1121
  lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, const boost::optional<std::unordered_map<string,string>> options) {
×
1122
      if (port < 0) {
×
1123
        port = 0;
×
1124
      }
×
1125
      if (port > std::numeric_limits<uint16_t>::max()) {
×
UNCOV
1126
        port = std::numeric_limits<uint16_t>::max();
×
1127
      }
×
1128

1129
      auto checker = [](const ComboAddress& addr, const opts_t& opts) {
×
1130
        return g_up.isUp(addr, opts);
×
1131
      };
×
UNCOV
1132
      return genericIfUp(ips, options, checker, port);
×
1133
    });
×
1134

UNCOV
1135
  lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) {
×
UNCOV
1136
      vector<ComboAddress> candidates;
×
1137
      opts_t opts;
×
UNCOV
1138
      if(options)
×
1139
        opts = *options;
×
1140

1141
      ComboAddress ca_unspec;
×
UNCOV
1142
      ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1143

1144
      // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1145
      for (const auto& [count, unitmap] : ipurls) {
×
1146
        // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1147
        vector<ComboAddress> available;
×
1148

1149
        for (const auto& [ipStr, url] : unitmap) {
×
1150
          // unit: ["192.0.2.1"] = "https://example.com"
1151
          ComboAddress ip(ipStr);
×
1152
          candidates.push_back(ip);
×
1153
          if (g_up.isUp(ca_unspec, url, opts)) {
×
UNCOV
1154
            available.push_back(ip);
×
UNCOV
1155
          }
×
1156
        }
×
1157
        if(!available.empty()) {
×
1158
          vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
UNCOV
1159
          return convComboAddressListToString(res);
×
1160
        }
×
1161
      }
×
1162

1163
      // All units down, apply backupSelector on all candidates
1164
      vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
×
1165
      return convComboAddressListToString(res);
×
1166
    });
×
1167

1168
  lua.writeFunction("ifurlup", [](const std::string& url,
×
UNCOV
1169
                                          const boost::variant<iplist_t, ipunitlist_t>& ips,
×
UNCOV
1170
                                          boost::optional<opts_t> options) {
×
1171

UNCOV
1172
    auto checker = [&url](const ComboAddress& addr, const opts_t& opts) {
×
1173
        return g_up.isUp(addr, url, opts);
×
1174
      };
×
1175
      return genericIfUp(ips, options, checker);
×
1176
    });
×
1177
  /*
1178
   * Returns a random IP address from the supplied list
1179
   * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
1180
   */
NEW
1181
  lua.writeFunction("pickrandom", [](const iplist_t& ips) {
×
NEW
1182
      vector<string> items = convStringList(ips);
×
NEW
1183
      return pickRandom<string>(items);
×
NEW
1184
    });
×
1185

1186
  /*
1187
   * Based on the hash of `bestwho`, returns an IP address from the list
1188
   * supplied, weighted according to the results of isUp calls.
1189
   * @example pickselfweighted('http://example.com/weight', { "192.0.2.20", "203.0.113.4", "203.0.113.2" })
1190
   */
NEW
1191
  lua.writeFunction("pickselfweighted", [](const std::string& url,
×
NEW
1192
                                             const iplist_t& ips,
×
NEW
1193
                                             boost::optional<opts_t> options) {
×
NEW
1194
      vector< pair<int, ComboAddress> > items;
×
NEW
1195
      opts_t opts;
×
NEW
1196
      if(options)
×
NEW
1197
        opts = *options;
×
1198

NEW
1199
      items.reserve(ips.capacity());
×
NEW
1200
      bool available = 0;
×
1201

NEW
1202
      vector<ComboAddress> conv = convComboAddressList(ips);
×
NEW
1203
      for (auto& entry : conv) {
×
NEW
1204
        int weight = 0;
×
NEW
1205
        weight = g_up.isUp(entry, url, opts);
×
NEW
1206
        if(weight>0){
×
NEW
1207
          available = 1;
×
NEW
1208
        }
×
NEW
1209
        items.emplace_back(weight, entry);
×
NEW
1210
      }
×
1211
      if(available) {
×
1212
        return pickWeightedHashed<ComboAddress>(s_lua_record_ctx->bestwho, items).toString();
×
1213
      }
×
1214

1215
      // All units down, apply backupSelector on all candidates
1216
      return pickWeightedRandom<ComboAddress>(items).toString();
×
1217
    });
×
1218

1219
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
×
UNCOV
1220
      vector<string> items = convStringList(ips);
×
UNCOV
1221
          return pickRandomSample<string>(n, items);
×
UNCOV
1222
    });
×
1223

UNCOV
1224
  lua.writeFunction("pickhashed", [](const iplist_t& ips) {
×
1225
      vector<string> items = convStringList(ips);
×
1226
      return pickHashed<string>(s_lua_record_ctx->bestwho, items);
×
1227
    });
×
1228
  /*
1229
   * Returns a random IP address from the supplied list, as weighted by the
1230
   * various ``weight`` parameters
1231
   * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1232
   */
UNCOV
1233
  lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
×
UNCOV
1234
      vector< pair<int, string> > items = convIntStringPairList(ips);
×
1235
      return pickWeightedRandom<string>(items);
×
1236
    });
×
1237

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

UNCOV
1246
      items.reserve(ips.size());
×
UNCOV
1247
      for (auto& entry : ips) {
×
UNCOV
1248
        items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
×
UNCOV
1249
      }
×
1250

1251
      return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1252
    });
×
1253

1254
  /*
1255
   * Based on the hash of the record name, return an IP address from the list
1256
   * supplied, as weighted by the various `weight` parameters
1257
   * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1258
   */
UNCOV
1259
  lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t > ips) {
×
1260
      vector< pair<int, string> > items;
×
1261

UNCOV
1262
      items.reserve(ips.size());
×
UNCOV
1263
      for(auto& i : ips)
×
UNCOV
1264
      {
×
UNCOV
1265
        items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
×
UNCOV
1266
      }
×
1267

1268
      return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
×
UNCOV
1269
    });
×
1270
  /*
1271
   * Based on the hash of `bestwho`, returns an IP address from the list
1272
   * supplied, as weighted by the various `weight` parameters and distributed consistently
1273
   * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1274
   */
1275
  lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) {
×
1276
    std::vector<std::pair<int, std::string>> items;
×
1277

1278
    items.reserve(ips.size());
×
1279
    for (const auto& entry : ips) {
×
UNCOV
1280
      items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
×
1281
    }
×
1282

1283
    return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
UNCOV
1284
  });
×
1285

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

1289
      return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1290

1291
    });
×
1292

1293
  if (g_luaRecordExecLimit > 0) {
×
1294
      lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1295
  }
×
1296

1297
  lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){
×
UNCOV
1298
      throw std::runtime_error("Script took too long");
×
1299
    });
×
1300

1301
  lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
×
1302
    return getGeo(ip, attr);
×
1303
  });
×
1304

1305
  typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
×
1306

1307
  lua.writeFunction("asnum", [](const combovar_t& asns) {
×
1308
      string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
×
1309
      return doCompare(asns, res, [](const std::string& a, const std::string& b) {
×
1310
          return !strcasecmp(a.c_str(), b.c_str());
×
1311
        });
×
1312
    });
×
1313
  lua.writeFunction("continent", [](const combovar_t& continent) {
×
1314
     string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1315
      return doCompare(continent, res, [](const std::string& a, const std::string& b) {
×
1316
          return !strcasecmp(a.c_str(), b.c_str());
×
1317
        });
×
1318
    });
×
1319
  lua.writeFunction("continentCode", []() {
×
1320
      string unknown("unknown");
×
1321
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1322
      if ( res == unknown ) {
×
1323
       return std::string("--");
×
UNCOV
1324
      }
×
1325
      return res;
×
1326
    });
×
1327
  lua.writeFunction("country", [](const combovar_t& var) {
×
1328
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1329
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1330
          return !strcasecmp(a.c_str(), b.c_str());
×
1331
        });
×
1332

1333
    });
×
1334
  lua.writeFunction("countryCode", []() {
×
1335
      string unknown("unknown");
×
1336
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1337
      if ( res == unknown ) {
×
1338
       return std::string("--");
×
UNCOV
1339
      }
×
1340
      return res;
×
1341
    });
×
1342
  lua.writeFunction("region", [](const combovar_t& var) {
×
1343
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1344
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1345
          return !strcasecmp(a.c_str(), b.c_str());
×
1346
        });
×
1347

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

1392
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
×
1393
      vector<string> result;
×
1394
          result.reserve(ips.size());
×
1395

UNCOV
1396
      for(const auto& ip : ips) {
×
1397
          result.emplace_back(ip.second);
×
1398
      }
×
1399
      if(result.empty()) {
×
1400
        throw std::invalid_argument("The IP list cannot be empty");
×
1401
      }
×
1402
      return result;
×
1403
    });
×
1404

1405
  lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) {
×
1406
    DNSName rec;
×
1407
    vector<string> ret;
×
1408
    try {
×
UNCOV
1409
      rec = DNSName(record);
×
1410
    }
×
1411
    catch (const std::exception& e) {
×
1412
      g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
×
UNCOV
1413
      return ret;
×
1414
    }
×
1415
    try {
×
1416
      SOAData soaData;
×
1417

1418
      if (!getAuth(rec, qtype, &soaData)) {
×
1419
        return ret;
×
1420
      }
×
1421

1422
      vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id);
×
1423
      for (const auto& drec : drs) {
×
UNCOV
1424
        ret.push_back(drec.dr.getContent()->getZoneRepresentation());
×
1425
      }
×
1426
    }
×
1427
    catch (std::exception& e) {
×
1428
      g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
×
1429
    }
×
1430
    return ret;
×
1431
  });
×
1432

1433
  lua.writeFunction("include", [&lua](string record) {
×
1434
      DNSName rec;
×
1435
      try {
×
1436
        rec = DNSName(record) + s_lua_record_ctx->zone;
×
1437
      } catch (const std::exception &e){
×
1438
        g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl;
×
1439
        return;
×
1440
      }
×
1441
      try {
×
1442
        vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zoneid);
×
1443
        for(const auto& dr : drs) {
×
1444
          auto lr = getRR<LUARecordContent>(dr.dr);
×
UNCOV
1445
          lua.executeCode(lr->getCode());
×
UNCOV
1446
        }
×
1447
      }
×
1448
      catch(std::exception& e) {
×
1449
        g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<rec<<": "<<e.what()<<endl;
×
1450
      }
×
1451
    });
×
1452
}
×
1453

1454
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)
UNCOV
1455
{
×
1456
  if(!LUA ||                  // we don't have a Lua state yet
×
UNCOV
1457
     !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1458
    LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
×
1459
    setupLuaRecords(*LUA->getLua());
×
1460
  }
×
1461

UNCOV
1462
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1463

1464
  LuaContext& lua = *LUA->getLua();
×
1465

1466
  s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1467
  s_lua_record_ctx->qname = query;
×
1468
  s_lua_record_ctx->zone = zone;
×
1469
  s_lua_record_ctx->zoneid = zoneid;
×
1470

1471
  lua.writeVariable("qname", query);
×
1472
  lua.writeVariable("zone", zone);
×
1473
  lua.writeVariable("zoneid", zoneid);
×
1474
  lua.writeVariable("who", dnsp.getInnerRemote());
×
1475
  lua.writeVariable("localwho", dnsp.getLocal());
×
1476
  lua.writeVariable("dh", (dnsheader*)&dnsp.d);
×
1477
  lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
×
1478
  lua.writeVariable("tcp", dnsp.d_tcp);
×
1479
  lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
×
1480
  if(dnsp.hasEDNSSubnet()) {
×
UNCOV
1481
    lua.writeVariable("ecswho", dnsp.getRealRemote());
×
1482
    s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
×
1483
  }
×
1484
  else {
×
1485
    lua.writeVariable("ecswho", nullptr);
×
1486
    s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
×
1487
  }
×
UNCOV
1488
  lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
×
1489

UNCOV
1490
  try {
×
1491
    string actual;
×
1492
    if(!code.empty() && code[0]!=';')
×
1493
      actual = "return " + code;
×
1494
    else
×
1495
      actual = code.substr(1);
×
1496

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

1499
    vector<string> contents;
×
1500
    if(auto str = boost::get<string>(&content))
×
1501
      contents.push_back(*str);
×
1502
    else
×
1503
      for(const auto& c : boost::get<vector<pair<int,string>>>(content))
×
1504
        contents.push_back(c.second);
×
1505

1506
    for(const auto& content_it: contents) {
×
1507
      if(qtype==QType::TXT)
×
1508
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
×
1509
      else
×
1510
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
×
1511
    }
×
1512
  } catch(std::exception &e) {
×
1513
    g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
×
1514
    try {
×
1515
      std::rethrow_if_nested(e);
×
1516
      g_log<<endl;
×
UNCOV
1517
    } catch(const std::exception& ne) {
×
1518
      g_log << ": " << ne.what() << std::endl;
×
1519
    }
×
UNCOV
1520
    catch(const PDNSException& ne) {
×
UNCOV
1521
      g_log << ": " << ne.reason << std::endl;
×
UNCOV
1522
    }
×
UNCOV
1523
    throw ;
×
UNCOV
1524
  }
×
1525

UNCOV
1526
  return ret;
×
UNCOV
1527
}
×
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