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

PowerDNS / pdns / 12268421405

11 Dec 2024 02:20AM UTC coverage: 64.728% (+3.2%) from 61.567%
12268421405

Pull #14954

github

web-flow
Merge d6414658d into 5089e2c08
Pull Request #14954: clang-tidy: use std::min/max

37522 of 88810 branches covered (42.25%)

Branch coverage included in aggregate %.

3 of 10 new or added lines in 4 files covered. (30.0%)

169 existing lines in 9 files now uncovered.

125906 of 163674 relevant lines covered (76.92%)

4802085.67 hits per line

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

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

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

25
   investigate IPv6
26

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

29
   ponder ECS scopemask setting
30

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

33
   unify ifurlup/ifportup
34
      add attribute for certificate check
35
   add list of current monitors
36
      expire them too?
37

38
   pool of UeberBackends?
39

40
   Pool checks ?
41
 */
42

43
extern int  g_luaRecordExecLimit;
44

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

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

66
      return std::tuple(rem, url, oopts) <
×
67
        std::tuple(rhs.rem, rhs.url, rhsoopts);
×
68
    }
×
69
  };
70
  struct CheckState
71
  {
72
    CheckState(time_t _lastAccess): lastAccess(_lastAccess) {}
×
73
    /* current status */
74
    std::atomic<bool> status{false};
75
    /* first check ? */
76
    std::atomic<bool> first{true};
77
    /* last time the status was accessed */
78
    std::atomic<time_t> lastAccess{0};
79
  };
80

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

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

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

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

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

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

188
          if (desc.url.empty()) { // TCP
×
189
            results.push_back(std::async(std::launch::async, &IsUpOracle::checkTCP, this, desc, state->status.load(), state->first.load()));
×
190
          } else { // URL
×
191
            results.push_back(std::async(std::launch::async, &IsUpOracle::checkURL, this, desc, state->status.load(), state->first.load()));
×
192
          }
×
193
          if (std::chrono::system_clock::from_time_t(state->lastAccess) < (checkStart - std::chrono::seconds(g_luaHealthChecksExpireDelay))) {
×
194
            toDelete.push_back(desc);
×
195
          }
×
196
        }
×
197
      }
×
198
      // we can release the lock as nothing will be deleted
199
      for (auto& future: results) {
×
200
        future.wait();
×
201
      }
×
202
      if (!toDelete.empty()) {
×
203
        auto statuses = d_statuses.write_lock();
×
204
        for (auto& it: toDelete) {
×
205
          statuses->erase(it);
×
206
        }
×
207
      }
×
208

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

212
      std::this_thread::sleep_until(checkStart + std::chrono::seconds(g_luaHealthChecksInterval));
×
213
    }
×
214
  }
×
215

216
  typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
217
  SharedLockGuarded<statuses_t> d_statuses;
218

219
  std::unique_ptr<std::thread> d_checkerThread;
220
  std::atomic_flag d_checkerThreadStarted;
221

222
  void setStatus(const CheckDesc& cd, bool status)
223
  {
×
224
    auto statuses = d_statuses.write_lock();
×
225
    auto& state = (*statuses)[cd];
×
226
    state->status = status;
×
227
    if (state->first) {
×
228
      state->first = false;
×
229
    }
×
230
  }
×
231

232
  void setDown(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
233
  {
×
234
    CheckDesc cd{rem, url, opts};
×
235
    setStatus(cd, false);
×
236
  }
×
237

238
  void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
239
  {
×
240
    CheckDesc cd{rem, url, opts};
×
241

×
242
    setStatus(cd, true);
×
243
  }
×
244

245
  void setDown(const CheckDesc& cd)
246
  {
×
247
    setStatus(cd, false);
×
248
  }
×
249

250
  void setUp(const CheckDesc& cd)
251
  {
×
252
    setStatus(cd, true);
×
253
  }
×
254
};
255

256
bool IsUpOracle::isUp(const CheckDesc& cd)
257
{
×
258
  if (!d_checkerThreadStarted.test_and_set()) {
×
259
    d_checkerThread = std::make_unique<std::thread>([this] { return checkThread(); });
×
260
  }
×
261
  time_t now = time(nullptr);
×
262
  {
×
263
    auto statuses = d_statuses.read_lock();
×
264
    auto iter = statuses->find(cd);
×
265
    if (iter != statuses->end()) {
×
266
      iter->second->lastAccess = now;
×
267
      return iter->second->status;
×
268
    }
×
269
  }
×
270
  // try to parse options so we don't insert any malformed content
271
  if (cd.opts.count("source")) {
×
272
    ComboAddress src(cd.opts.at("source"));
×
273
  }
×
274
  {
×
275
    auto statuses = d_statuses.write_lock();
×
276
    // Make sure we don't insert new entry twice now we have the lock
277
    if (statuses->find(cd) == statuses->end()) {
×
278
      (*statuses)[cd] = std::make_unique<CheckState>(now);
×
279
    }
×
280
  }
×
281
  return false;
×
282
}
×
283

284
bool IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts)
285
{
×
286
  CheckDesc cd{remote, "", opts};
×
287
  return isUp(cd);
×
288
}
×
289

290
bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts)
291
{
×
292
  CheckDesc cd{remote, url, opts};
×
293
  return isUp(cd);
×
294
}
×
295

296
IsUpOracle g_up;
297
namespace {
298
template<typename T, typename C>
299
bool doCompare(const T& var, const std::string& res, const C& cmp)
300
{
×
301
  if(auto country = boost::get<string>(&var))
×
302
    return cmp(*country, res);
×
303

304
  auto countries=boost::get<vector<pair<int,string> > >(&var);
×
305
  for(const auto& country : *countries) {
×
306
    if(cmp(country.second, res))
×
307
      return true;
×
308
  }
×
309
  return false;
×
310
}
×
311
}
312

313
static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
314
{
×
315
  static bool initialized;
×
316
  extern std::function<std::string(const std::string& ip, int)> g_getGeo;
×
317
  if(!g_getGeo) {
×
318
    if(!initialized) {
×
319
      g_log<<Logger::Error<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl;
×
320
      initialized=true;
×
321
    }
×
322
    return "unknown";
×
323
  }
×
324
  else
×
325
    return g_getGeo(ip, (int)qa);
×
326
}
×
327

328
template <typename T>
329
static T pickRandom(const vector<T>& items)
330
{
×
331
  if (items.empty()) {
×
332
    throw std::invalid_argument("The items list cannot be empty");
×
333
  }
×
334
  return items[dns_random(items.size())];
×
335
}
×
336

337
template <typename T>
338
static T pickHashed(const ComboAddress& who, const vector<T>& items)
339
{
×
340
  if (items.empty()) {
×
341
    throw std::invalid_argument("The items list cannot be empty");
×
342
  }
×
343
  ComboAddress::addressOnlyHash aoh;
×
344
  return items[aoh(who) % items.size()];
×
345
}
×
346

347
template <typename T>
348
static T pickWeightedRandom(const vector< pair<int, T> >& items)
349
{
×
350
  if (items.empty()) {
×
351
    throw std::invalid_argument("The items list cannot be empty");
×
352
  }
×
353
  int sum=0;
×
354
  vector< pair<int, T> > pick;
×
355
  pick.reserve(items.size());
×
356

357
  for(auto& i : items) {
×
358
    sum += i.first;
×
359
    pick.emplace_back(sum, i.second);
×
360
  }
×
361

362
  if (sum == 0) {
×
363
    throw std::invalid_argument("The sum of items cannot be zero");
×
364
  }
×
365

366
  int r = dns_random(sum);
×
367
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
368
  return p->second;
×
369
}
×
370

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

381
  for(auto& i : items) {
×
382
    sum += i.first;
×
383
    pick.push_back({sum, i.second});
×
384
  }
×
385

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

390
  ComboAddress::addressOnlyHash aoh;
×
391
  int r = aoh(bestwho) % sum;
×
392
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
393
  return p->second;
×
394
}
×
395

396
template <typename T>
397
static T pickWeightedNameHashed(const DNSName& dnsname, vector< pair<int, T> >& items)
398
{
×
399
  if (items.empty()) {
×
400
    throw std::invalid_argument("The items list cannot be empty");
×
401
  }
×
402
  size_t sum=0;
×
403
  vector< pair<int, T> > pick;
×
404
  pick.reserve(items.size());
×
405

406
  for(auto& i : items) {
×
407
    sum += i.first;
×
408
    pick.push_back({sum, i.second});
×
409
  }
×
410

411
  if (sum == 0) {
×
412
    throw std::invalid_argument("The sum of items cannot be zero");
×
413
  }
×
414

415
  size_t r = dnsname.hash() % sum;
×
416
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
417
  return p->second;
×
418
}
×
419

420
template <typename T>
421
static vector<T> pickRandomSample(int n, const vector<T>& items)
422
{
×
423
  if (items.empty()) {
×
424
    throw std::invalid_argument("The items list cannot be empty");
×
425
  }
×
426

427
  vector<T> pick;
×
428
  pick.reserve(items.size());
×
429

430
  for(auto& item : items) {
×
431
    pick.push_back(item);
×
432
  }
×
433

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

436
  if (count == 0) {
×
437
    return vector<T>();
×
438
  }
×
439

440
  std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
×
441

442
  vector<T> result = {pick.begin(), pick.begin() + count};
×
443
  return result;
×
444
}
×
445

446
static bool getLatLon(const std::string& ip, double& lat, double& lon)
447
{
×
448
  string inp = getGeo(ip, GeoIPInterface::Location);
×
449
  if(inp.empty())
×
450
    return false;
×
451
  lat=atof(inp.c_str());
×
452
  auto pos=inp.find(' ');
×
453
  if(pos != string::npos)
×
454
    lon=atof(inp.c_str() + pos);
×
455
  return true;
×
456
}
×
457

458
static bool getLatLon(const std::string& ip, string& loc)
459
{
×
460
  int latdeg, latmin, londeg, lonmin;
×
461
  double latsec, lonsec;
×
462
  char lathem='X', lonhem='X';
×
463

464
  double lat = 0, lon = 0;
×
465
  if(!getLatLon(ip, lat, lon))
×
466
    return false;
×
467

468
  if(lat > 0) {
×
469
    lathem='N';
×
470
  }
×
471
  else {
×
472
    lat = -lat;
×
473
    lathem='S';
×
474
  }
×
475

476
  if(lon > 0) {
×
477
    lonhem='E';
×
478
  }
×
479
  else {
×
480
    lon = -lon;
×
481
    lonhem='W';
×
482
  }
×
483

484
  latdeg = lat;
×
485
  latmin = (lat - latdeg)*60.0;
×
486
  latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
×
487

488
  londeg = lon;
×
489
  lonmin = (lon - londeg)*60.0;
×
490
  lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
×
491

492
  // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
493

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

496
  loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
×
497
  return true;
×
498
}
×
499

500
static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips)
501
{
×
502
  if (wips.empty()) {
×
503
    throw std::invalid_argument("The IP list cannot be empty");
×
504
  }
×
505
  map<double, vector<ComboAddress> > ranked;
×
506
  double wlat=0, wlon=0;
×
507
  getLatLon(bestwho.toString(), wlat, wlon);
×
508
  //        cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
509
  vector<string> ret;
×
510
  for(const auto& c : wips) {
×
511
    double lat=0, lon=0;
×
512
    getLatLon(c.toString(), lat, lon);
×
513
    //          cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
514
    double latdiff = wlat-lat;
×
515
    double londiff = wlon-lon;
×
516
    if(londiff > 180)
×
517
      londiff = 360 - londiff;
×
518
    double dist2=latdiff*latdiff + londiff*londiff;
×
519
    //          cout<<"    distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
520
    ranked[dist2].push_back(c);
×
521
  }
×
522
  return ranked.begin()->second[dns_random(ranked.begin()->second.size())];
×
523
}
×
524

525
static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid)
526
{
×
527
  static LockGuarded<UeberBackend> s_ub;
×
528

529
  DNSZoneRecord dr;
×
530
  vector<DNSZoneRecord> ret;
×
531
  {
×
532
    auto ub = s_ub.lock();
×
533
    ub->lookup(QType(qtype), name, zoneid);
×
534
    while (ub->get(dr)) {
×
535
      ret.push_back(dr);
×
536
    }
×
537
  }
×
538
  return ret;
×
539
}
×
540

541
static bool getAuth(const DNSName& name, uint16_t qtype, SOAData* soaData)
542
{
×
543
  static LockGuarded<UeberBackend> s_ub;
×
544

545
  {
×
546
    auto ueback = s_ub.lock();
×
547
    return ueback->getAuth(name, qtype, soaData);
×
548
  }
×
549
}
×
550

551
static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
552
{
×
553
  string selector=defaultValue;
×
554
  if(options) {
×
555
    if(options->count(name))
×
556
      selector=options->find(name)->second;
×
557
  }
×
558
  return selector;
×
559
}
×
560

561
static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
562
{
×
563
  vector<ComboAddress> ret;
×
564

565
  if(selector=="all")
×
566
    return candidates;
×
567
  else if(selector=="random")
×
568
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
569
  else if(selector=="pickclosest")
×
570
    ret.emplace_back(pickclosest(bestwho, candidates));
×
571
  else if(selector=="hashed")
×
572
    ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates));
×
573
  else {
×
574
    g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl;
×
575
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
576
  }
×
577

578
  return ret;
×
579
}
×
580

581
static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
582
{
×
583
  vector<string> result;
×
584
  result.reserve(items.size());
×
585

586
  for (const auto& item : items) {
×
587
    result.emplace_back(item.toString());
×
588
  }
×
589

590
  return result;
×
591
}
×
592

593
static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
594
{
×
595
  vector<ComboAddress> result;
×
596
  result.reserve(items.size());
×
597

598
  for(const auto& item : items) {
×
599
    result.emplace_back(ComboAddress(item.second, port));
×
600
  }
×
601

602
  return result;
×
603
}
×
604

605
/**
606
 * Reads and unify single or multiple sets of ips :
607
 * - {'192.0.2.1', '192.0.2.2'}
608
 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
609
 */
610

611
static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
612
{
×
613
  vector<vector<ComboAddress>> candidates;
×
614

615
  if(auto simple = boost::get<iplist_t>(&items)) {
×
616
    vector<ComboAddress> unit = convComboAddressList(*simple, port);
×
617
    candidates.push_back(unit);
×
618
  } else {
×
619
    auto units = boost::get<ipunitlist_t>(items);
×
620
    for(const auto& u : units) {
×
621
      vector<ComboAddress> unit = convComboAddressList(u.second, port);
×
622
      candidates.push_back(unit);
×
623
    }
×
624
  }
×
625
  return candidates;
×
626
}
×
627

628
static vector<string> convStringList(const iplist_t& items)
629
{
×
630
  vector<string> result;
×
631
  result.reserve(items.size());
×
632

633
  for(const auto& item : items) {
×
634
    result.emplace_back(item.second);
×
635
  }
×
636

637
  return result;
×
638
}
×
639

640
static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
641
{
×
642
  vector<pair<int,string> > result;
×
643
  result.reserve(items.size());
×
644

645
  for(const auto& item : items) {
×
646
    result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
×
647
  }
×
648

649
  return result;
×
650
}
×
651

652
bool g_LuaRecordSharedState;
653

654
typedef struct AuthLuaRecordContext
655
{
656
  ComboAddress          bestwho;
657
  DNSName               qname;
658
  DNSName               zone;
659
  int                   zoneid;
660
} lua_record_ctx_t;
661

662
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
663

664
/*
665
 *  Holds computed hashes for a given entry
666
 */
667
struct EntryHashesHolder
668
{
669
  std::atomic<size_t> weight;
670
  std::string entry;
671
  SharedLockGuarded<std::vector<unsigned int>> hashes;
672
  std::atomic<time_t> lastUsed;
673

674
  EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
×
675
  }
×
676

677
  bool hashesComputed() {
×
678
    return weight == hashes.read_lock()->size();
×
679
  }
×
680
  void hash() {
×
681
    auto locked = hashes.write_lock();
×
682
    locked->clear();
×
683
    locked->reserve(weight);
×
684
    size_t count = 0;
×
685
    while (count < weight) {
×
686
      auto value = boost::str(boost::format("%s-%d") % entry % count);
×
687
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
688
      auto whash = burtle(reinterpret_cast<const unsigned char*>(value.data()), value.size(), 0);
×
689
      locked->push_back(whash);
×
690
      ++count;
×
691
    }
×
692
    std::sort(locked->begin(), locked->end());
×
693
  }
×
694
};
695

696
using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
697

698
static SharedLockGuarded<std::map<
699
  zone_hashes_key_t, // zoneid qname entry
700
  std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
701
  >>
702
s_zone_hashes;
703

704
static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
705

706
/**
707
 * every ~g_luaConsistentHashesCleanupInterval, do a cleanup to delete entries that haven't been used in the last g_luaConsistentHashesExpireDelay
708
 */
709
static void cleanZoneHashes()
710
{
×
711
  auto now = time(nullptr);
×
712
  if (s_lastConsistentHashesCleanup > (now - g_luaConsistentHashesCleanupInterval)) {
×
713
    return ;
×
714
  }
×
715
  s_lastConsistentHashesCleanup = now;
×
716
  std::vector<zone_hashes_key_t> toDelete{};
×
717
  {
×
718
    auto locked = s_zone_hashes.read_lock();
×
719
    auto someTimeAgo = now - g_luaConsistentHashesExpireDelay;
×
720

721
    for (const auto& [key, entry]: *locked) {
×
722
      if (entry->lastUsed > someTimeAgo) {
×
723
        toDelete.push_back(key);
×
724
      }
×
725
    }
×
726
  }
×
727
  if (!toDelete.empty()) {
×
728
    auto wlocked = s_zone_hashes.write_lock();
×
729
    for (const auto& key : toDelete) {
×
730
      wlocked->erase(key);
×
731
    }
×
732
  }
×
733
}
×
734

735
static std::vector<std::shared_ptr<EntryHashesHolder>> getCHashedEntries(const int zoneId, const std::string& queryName, const std::vector<std::pair<int, std::string>>& items)
736
{
×
737
  std::vector<std::shared_ptr<EntryHashesHolder>> result{};
×
738
  std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
×
739

740
  {
×
741
    time_t now = time(nullptr);
×
742
    auto locked = s_zone_hashes.read_lock();
×
743

744
    for (const auto& [weight, entry]: items) {
×
745
      auto key = std::make_tuple(zoneId, queryName, entry);
×
746
      if (locked->count(key) == 0) {
×
747
        newEntries[key] = std::make_shared<EntryHashesHolder>(weight, entry, now);
×
748
      } else {
×
749
        locked->at(key)->weight = weight;
×
750
        locked->at(key)->lastUsed = now;
×
751
        result.push_back(locked->at(key));
×
752
      }
×
753
    }
×
754
  }
×
755
  if (!newEntries.empty()) {
×
756
    auto wlocked = s_zone_hashes.write_lock();
×
757

758
    for (auto& [key, entry]: newEntries) {
×
759
      result.push_back(entry);
×
760
      (*wlocked)[key] = std::move(entry);
×
761
    }
×
762
  }
×
763

764
  return result;
×
765
}
×
766

767
static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
768
{
×
769
  const auto& zoneId = s_lua_record_ctx->zoneid;
×
770
  const auto queryName = s_lua_record_ctx->qname.toString();
×
771
  unsigned int sel = std::numeric_limits<unsigned int>::max();
×
772
  unsigned int min = std::numeric_limits<unsigned int>::max();
×
773

774
  boost::optional<std::string> ret;
×
775
  boost::optional<std::string> first;
×
776

777
  cleanZoneHashes();
×
778

779
  auto entries = getCHashedEntries(zoneId, queryName, items);
×
780

781
  ComboAddress::addressOnlyHash addrOnlyHash;
×
782
  auto qhash = addrOnlyHash(bestwho);
×
783
  for (const auto& entry : entries) {
×
784
    if (!entry->hashesComputed()) {
×
785
      entry->hash();
×
786
    }
×
787
    {
×
788
      const auto hashes = entry->hashes.read_lock();
×
789
      if (!hashes->empty()) {
×
790
        if (min > *(hashes->begin())) {
×
791
          min = *(hashes->begin());
×
792
          first = entry->entry;
×
793
        }
×
794

795
        auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
×
796
        if (hash_it != hashes->end()) {
×
797
          if (*hash_it < sel) {
×
798
            sel = *hash_it;
×
799
            ret = entry->entry;
×
800
          }
×
801
        }
×
802
      }
×
803
    }
×
804
  }
×
805
  if (ret != boost::none) {
×
806
    return *ret;
×
807
  }
×
808
  if (first != boost::none) {
×
809
    return *first;
×
810
  }
×
811
  return {};
×
812
}
×
813

814
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)
815
{
×
816
  vector<vector<ComboAddress> > candidates;
×
817
  opts_t opts;
×
818
  if(options)
×
819
    opts = *options;
×
820

821
  candidates = convMultiComboAddressList(ips, port);
×
822

823
  for(const auto& unit : candidates) {
×
824
    vector<ComboAddress> available;
×
825
    for(const auto& c : unit) {
×
826
      if (upcheckf(c, opts)) {
×
827
        available.push_back(c);
×
828
      }
×
829
    }
×
830
    if(!available.empty()) {
×
831
      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
832
      return convComboAddressListToString(res);
×
833
    }
×
834
  }
×
835

836
  // All units down, apply backupSelector on all candidates
837
  vector<ComboAddress> ret{};
×
838
  for(const auto& unit : candidates) {
×
839
    ret.insert(ret.end(), unit.begin(), unit.end());
×
840
  }
×
841

842
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
×
843
  return convComboAddressListToString(res);
×
844
}
×
845

846
static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cognitive-complexity)
847
{
×
848
  lua.writeFunction("latlon", []() {
×
849
      double lat = 0, lon = 0;
×
850
      getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
×
851
      return std::to_string(lat)+" "+std::to_string(lon);
×
852
    });
×
853
  lua.writeFunction("latlonloc", []() {
×
854
      string loc;
×
855
      getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
×
856
      return loc;
×
857
  });
×
858
  lua.writeFunction("closestMagic", []() {
×
859
      vector<ComboAddress> candidates;
×
860
      // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
861
      for(auto l : s_lua_record_ctx->qname.getRawLabels()) {
×
862
        boost::replace_all(l, "-", ".");
×
863
        try {
×
864
          candidates.emplace_back(l);
×
865
        } catch (const PDNSException& e) {
×
866
          // no need to continue as we most likely reached the end of the ip list
867
          break ;
×
868
        }
×
869
      }
×
870
      return pickclosest(s_lua_record_ctx->bestwho, candidates).toString();
×
871
    });
×
872
  lua.writeFunction("latlonMagic", [](){
×
873
      auto labels= s_lua_record_ctx->qname.getRawLabels();
×
874
      if(labels.size()<4)
×
875
        return std::string("unknown");
×
876
      double lat = 0, lon = 0;
×
877
      getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
×
878
      return std::to_string(lat)+" "+std::to_string(lon);
×
879
    });
×
880

881

882
  lua.writeFunction("createReverse", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
883
      try {
×
884
        auto labels = s_lua_record_ctx->qname.getRawLabels();
×
885
        if(labels.size()<4)
×
886
          return std::string("unknown");
×
887

888
        vector<ComboAddress> candidates;
×
889

890
        // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
891
        // e["1.2.3.4"]="bert.powerdns.com" then provides an exception
892
        if(e) {
×
893
          ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
×
894
          const auto& uom = *e;
×
895
          for(const auto& c : uom)
×
896
            if(ComboAddress(c.first, 0) == req)
×
897
              return c.second;
×
898
        }
×
899
        boost::format fmt(format);
×
900
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
901
        fmt % labels[3] % labels[2] % labels[1] % labels[0];
×
902

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

905
        boost::format fmt2("%02x%02x%02x%02x");
×
906
        for(int i=3; i>=0; --i)
×
907
          fmt2 % atoi(labels[i].c_str());
×
908

909
        fmt % (fmt2.str());
×
910

911
        return fmt.str();
×
912
      }
×
913
      catch(std::exception& ex) {
×
914
        g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
×
915
      }
×
916
      return std::string("error");
×
917
    });
×
918
  lua.writeFunction("createForward", []() {
×
919
      static string allZerosIP("0.0.0.0");
×
920
      DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
×
921
      // parts is something like ["1", "2", "3", "4", "static"] or
922
      // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
923
      auto parts = rel.getRawLabels();
×
924
      // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
925
      if(parts.size()>=4) {
×
926
        try {
×
927
          ComboAddress ca(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
×
928
          return ca.toString();
×
929
        } catch (const PDNSException &e) {
×
930
          return allZerosIP;
×
931
        }
×
932
      } else if (!parts.empty()) {
×
933
        auto& input = parts.at(0);
×
934

935
        // allow a word without - in front, as long as it does not contain anything that could be a number
936
        size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
×
937
        if (nonhexprefix > 0) {
×
938
          input = input.substr(nonhexprefix);
×
939
        }
×
940

941
        // either hex string, or 12-13-14-15
942
        vector<string> ip_parts;
×
943

944
        stringtok(ip_parts, input, "-");
×
945
        unsigned int x1, x2, x3, x4;
×
946
        if (ip_parts.size() >= 4) {
×
947
          // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
948
          string ret;
×
949
          for (size_t index=4; index > 0; index--) {
×
950
            auto octet = ip_parts[ip_parts.size() - index];
×
951
            try {
×
952
              auto octetVal = std::stol(octet);
×
953
              if (octetVal >= 0 && octetVal <= 255) {
×
954
                ret += ip_parts.at(ip_parts.size() - index) + ".";
×
955
              } else {
×
956
                return allZerosIP;
×
957
              }
×
958
            } catch (const std::exception &e) {
×
959
              return allZerosIP;
×
960
            }
×
961
          }
×
962
          ret.resize(ret.size() - 1); // remove trailing dot after last octet
×
963
          return ret;
×
964
        }
×
965
        if(input.length() >= 8) {
×
966
          auto last8 = input.substr(input.length()-8);
×
967
          if(sscanf(last8.c_str(), "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
×
968
            return std::to_string(x1) + "." + std::to_string(x2) + "." + std::to_string(x3) + "." + std::to_string(x4);
×
969
          }
×
970
        }
×
971
      }
×
972
      return allZerosIP;
×
973
    });
×
974

975
  lua.writeFunction("createForward6", []() {
×
976
      DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
×
977
      auto parts = rel.getRawLabels();
×
978
      if(parts.size()==8) {
×
979
        string tot;
×
980
        for(int i=0; i<8; ++i) {
×
981
          if(i)
×
982
            tot.append(1,':');
×
983
          tot+=parts[i];
×
984
        }
×
985
        ComboAddress ca(tot);
×
986
        return ca.toString();
×
987
      }
×
988
      else if(parts.size()==1) {
×
989
        if (parts[0].find('-') != std::string::npos) {
×
990
          boost::replace_all(parts[0],"-",":");
×
991
          ComboAddress ca(parts[0]);
×
992
          return ca.toString();
×
993
        } else {
×
994
          if (parts[0].size() >= 32) {
×
995
            auto ippart = parts[0].substr(parts[0].size()-32);
×
996
            auto fulladdress =
×
997
              ippart.substr(0, 4) + ":" +
×
998
              ippart.substr(4, 4) + ":" +
×
999
              ippart.substr(8, 4) + ":" +
×
1000
              ippart.substr(12, 4) + ":" +
×
1001
              ippart.substr(16, 4) + ":" +
×
1002
              ippart.substr(20, 4) + ":" +
×
1003
              ippart.substr(24, 4) + ":" +
×
1004
              ippart.substr(28, 4);
×
1005

1006
            ComboAddress ca(fulladdress);
×
1007
            return ca.toString();
×
1008
          }
×
1009
        }
×
1010
      }
×
1011

1012
      return std::string("::");
×
1013
    });
×
1014
  lua.writeFunction("createReverse6", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
1015
      vector<ComboAddress> candidates;
×
1016

1017
      try {
×
1018
        auto labels= s_lua_record_ctx->qname.getRawLabels();
×
1019
        if(labels.size()<32)
×
1020
          return std::string("unknown");
×
1021
        boost::format fmt(format);
×
1022
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
1023

1024

1025
        string together;
×
1026
        vector<string> quads;
×
1027
        for(int i=0; i<8; ++i) {
×
1028
          if(i)
×
1029
            together+=":";
×
1030
          string lquad;
×
1031
          for(int j=0; j <4; ++j) {
×
1032
            lquad.append(1, labels[31-i*4-j][0]);
×
1033
            together += labels[31-i*4-j][0];
×
1034
          }
×
1035
          quads.push_back(lquad);
×
1036
        }
×
1037
        ComboAddress ip6(together,0);
×
1038

1039
        if(e) {
×
1040
          auto& addrs=*e;
×
1041
          for(const auto& addr: addrs) {
×
1042
            // this makes sure we catch all forms of the address
1043
            if(ComboAddress(addr.first,0)==ip6)
×
1044
              return addr.second;
×
1045
          }
×
1046
        }
×
1047

1048
        string dashed=ip6.toString();
×
1049
        boost::replace_all(dashed, ":", "-");
×
1050

1051
        for(int i=31; i>=0; --i)
×
1052
          fmt % labels[i];
×
1053
        fmt % dashed;
×
1054

1055
        for(const auto& lquad : quads)
×
1056
          fmt % lquad;
×
1057

1058
        return fmt.str();
×
1059
      }
×
1060
      catch(std::exception& ex) {
×
1061
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.what()<<endl;
×
1062
      }
×
1063
      catch(PDNSException& ex) {
×
1064
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl;
×
1065
      }
×
1066
      return std::string("unknown");
×
1067
    });
×
1068

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

1072
      if (nmg.match(ComboAddress(address))) {
×
1073
        return {address};
×
1074
      } else {
×
1075
        if (fallback) {
×
1076
          if (fallback->empty()) {
×
1077
            // if fallback is an empty string, return an empty array
1078
            return {};
×
1079
          }
×
1080
          return {*fallback};
×
1081
        }
×
1082

1083
        if (ca.isIPv4()) {
×
1084
          return {string("0.0.0.0")};
×
1085
        } else {
×
1086
          return {string("::")};
×
1087
        }
×
1088
      }
×
1089
    });
×
1090

1091
  /*
1092
   * Simplistic test to see if an IP address listens on a certain port
1093
   * Will return a single IP address from the set of available IP addresses. If
1094
   * no IP address is available, will return a random element of the set of
1095
   * addresses supplied for testing.
1096
   *
1097
   * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
1098
   */
NEW
1099
  lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, const boost::optional<std::unordered_map<string, string>>& options) {
×
NEW
1100
    port = std::clamp(port, 0, static_cast<int>(std::numeric_limits<uint16_t>::max()));
×
1101

NEW
1102
    auto checker = [](const ComboAddress& addr, const opts_t& opts) {
×
NEW
1103
      return g_up.isUp(addr, opts);
×
NEW
1104
    };
×
NEW
1105
    return genericIfUp(ips, options, checker, port);
×
NEW
1106
  });
×
1107

1108
  lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) {
×
1109
      vector<ComboAddress> candidates;
×
1110
      opts_t opts;
×
1111
      if(options)
×
1112
        opts = *options;
×
1113

1114
      ComboAddress ca_unspec;
×
1115
      ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1116

1117
      // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1118
      for (const auto& [count, unitmap] : ipurls) {
×
1119
        // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1120
        vector<ComboAddress> available;
×
1121

1122
        for (const auto& [ipStr, url] : unitmap) {
×
1123
          // unit: ["192.0.2.1"] = "https://example.com"
1124
          ComboAddress ip(ipStr);
×
1125
          candidates.push_back(ip);
×
1126
          if (g_up.isUp(ca_unspec, url, opts)) {
×
1127
            available.push_back(ip);
×
1128
          }
×
1129
        }
×
1130
        if(!available.empty()) {
×
1131
          vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
1132
          return convComboAddressListToString(res);
×
1133
        }
×
1134
      }
×
1135

1136
      // All units down, apply backupSelector on all candidates
1137
      vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
×
1138
      return convComboAddressListToString(res);
×
1139
    });
×
1140

1141
  lua.writeFunction("ifurlup", [](const std::string& url,
×
1142
                                          const boost::variant<iplist_t, ipunitlist_t>& ips,
×
1143
                                          boost::optional<opts_t> options) {
×
1144

1145
    auto checker = [&url](const ComboAddress& addr, const opts_t& opts) {
×
1146
        return g_up.isUp(addr, url, opts);
×
1147
      };
×
1148
      return genericIfUp(ips, options, checker);
×
1149
    });
×
1150
  /*
1151
   * Returns a random IP address from the supplied list
1152
   * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
1153
   */
1154
  lua.writeFunction("pickrandom", [](const iplist_t& ips) {
×
1155
      vector<string> items = convStringList(ips);
×
1156
      return pickRandom<string>(items);
×
1157
    });
×
1158

1159
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
×
1160
      vector<string> items = convStringList(ips);
×
1161
          return pickRandomSample<string>(n, items);
×
1162
    });
×
1163

1164
  lua.writeFunction("pickhashed", [](const iplist_t& ips) {
×
1165
      vector<string> items = convStringList(ips);
×
1166
      return pickHashed<string>(s_lua_record_ctx->bestwho, items);
×
1167
    });
×
1168
  /*
1169
   * Returns a random IP address from the supplied list, as weighted by the
1170
   * various ``weight`` parameters
1171
   * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1172
   */
1173
  lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
×
1174
      vector< pair<int, string> > items = convIntStringPairList(ips);
×
1175
      return pickWeightedRandom<string>(items);
×
1176
    });
×
1177

1178
  /*
1179
   * Based on the hash of `bestwho`, returns an IP address from the list
1180
   * supplied, as weighted by the various `weight` parameters
1181
   * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1182
   */
1183
  lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) {
×
1184
      vector< pair<int, string> > items;
×
1185

1186
      items.reserve(ips.size());
×
1187
      for (auto& entry : ips) {
×
1188
        items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
×
1189
      }
×
1190

1191
      return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1192
    });
×
1193

1194
  /*
1195
   * Based on the hash of the record name, return an IP address from the list
1196
   * supplied, as weighted by the various `weight` parameters
1197
   * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1198
   */
1199
  lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t > ips) {
×
1200
      vector< pair<int, string> > items;
×
1201

1202
      items.reserve(ips.size());
×
1203
      for(auto& i : ips)
×
1204
      {
×
1205
        items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
×
1206
      }
×
1207

1208
      return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
×
1209
    });
×
1210
  /*
1211
   * Based on the hash of `bestwho`, returns an IP address from the list
1212
   * supplied, as weighted by the various `weight` parameters and distributed consistently
1213
   * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1214
   */
1215
  lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) {
×
1216
    std::vector<std::pair<int, std::string>> items;
×
1217

1218
    items.reserve(ips.size());
×
1219
    for (const auto& entry : ips) {
×
1220
      items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
×
1221
    }
×
1222

1223
    return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1224
  });
×
1225

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

1229
      return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1230

1231
    });
×
1232

1233
  if (g_luaRecordExecLimit > 0) {
×
1234
      lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1235
  }
×
1236

1237
  lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){
×
1238
      throw std::runtime_error("Script took too long");
×
1239
    });
×
1240

1241
  lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
×
1242
    return getGeo(ip, attr);
×
1243
  });
×
1244

1245
  typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
×
1246

1247
  lua.writeFunction("asnum", [](const combovar_t& asns) {
×
1248
      string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
×
1249
      return doCompare(asns, res, [](const std::string& a, const std::string& b) {
×
1250
          return !strcasecmp(a.c_str(), b.c_str());
×
1251
        });
×
1252
    });
×
1253
  lua.writeFunction("continent", [](const combovar_t& continent) {
×
1254
     string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1255
      return doCompare(continent, res, [](const std::string& a, const std::string& b) {
×
1256
          return !strcasecmp(a.c_str(), b.c_str());
×
1257
        });
×
1258
    });
×
1259
  lua.writeFunction("continentCode", []() {
×
1260
      string unknown("unknown");
×
1261
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1262
      if ( res == unknown ) {
×
1263
       return std::string("--");
×
1264
      }
×
1265
      return res;
×
1266
    });
×
1267
  lua.writeFunction("country", [](const combovar_t& var) {
×
1268
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1269
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1270
          return !strcasecmp(a.c_str(), b.c_str());
×
1271
        });
×
1272

1273
    });
×
1274
  lua.writeFunction("countryCode", []() {
×
1275
      string unknown("unknown");
×
1276
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1277
      if ( res == unknown ) {
×
1278
       return std::string("--");
×
1279
      }
×
1280
      return res;
×
1281
    });
×
1282
  lua.writeFunction("region", [](const combovar_t& var) {
×
1283
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1284
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1285
          return !strcasecmp(a.c_str(), b.c_str());
×
1286
        });
×
1287

1288
    });
×
1289
  lua.writeFunction("regionCode", []() {
×
1290
      string unknown("unknown");
×
1291
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1292
      if ( res == unknown ) {
×
1293
       return std::string("--");
×
1294
      }
×
1295
      return res;
×
1296
    });
×
1297
  lua.writeFunction("netmask", [](const iplist_t& ips) {
×
1298
      for(const auto& i :ips) {
×
1299
        Netmask nm(i.second);
×
1300
        if(nm.match(s_lua_record_ctx->bestwho))
×
1301
          return true;
×
1302
      }
×
1303
      return false;
×
1304
    });
×
1305
  /* {
1306
       {
1307
        {'192.168.0.0/16', '10.0.0.0/8'},
1308
        {'192.168.20.20', '192.168.20.21'}
1309
       },
1310
       {
1311
        {'0.0.0.0/0'}, {'192.0.2.1'}
1312
       }
1313
     }
1314
  */
1315
  lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) {
×
1316
      for(const auto& rule : in) {
×
1317
        const auto& netmasks=rule.second[0].second;
×
1318
        const auto& destinations=rule.second[1].second;
×
1319
        for(const auto& nmpair : netmasks) {
×
1320
          Netmask nm(nmpair.second);
×
1321
          if(nm.match(s_lua_record_ctx->bestwho)) {
×
1322
            if (destinations.empty()) {
×
1323
              throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm.toString() + ")");
×
1324
            }
×
1325
            return destinations[dns_random(destinations.size())].second;
×
1326
          }
×
1327
        }
×
1328
      }
×
1329
      return std::string();
×
1330
    });
×
1331

1332
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
×
1333
      vector<string> result;
×
1334
          result.reserve(ips.size());
×
1335

1336
      for(const auto& ip : ips) {
×
1337
          result.emplace_back(ip.second);
×
1338
      }
×
1339
      if(result.empty()) {
×
1340
        throw std::invalid_argument("The IP list cannot be empty");
×
1341
      }
×
1342
      return result;
×
1343
    });
×
1344

1345
  lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) {
×
1346
    DNSName rec;
×
1347
    vector<string> ret;
×
1348
    try {
×
1349
      rec = DNSName(record);
×
1350
    }
×
1351
    catch (const std::exception& e) {
×
1352
      g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
×
1353
      return ret;
×
1354
    }
×
1355
    try {
×
1356
      SOAData soaData;
×
1357

1358
      if (!getAuth(rec, qtype, &soaData)) {
×
1359
        return ret;
×
1360
      }
×
1361

1362
      vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id);
×
1363
      for (const auto& drec : drs) {
×
1364
        ret.push_back(drec.dr.getContent()->getZoneRepresentation());
×
1365
      }
×
1366
    }
×
1367
    catch (std::exception& e) {
×
1368
      g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
×
1369
    }
×
1370
    return ret;
×
1371
  });
×
1372

1373
  lua.writeFunction("include", [&lua](string record) {
×
1374
      DNSName rec;
×
1375
      try {
×
1376
        rec = DNSName(record) + s_lua_record_ctx->zone;
×
1377
      } catch (const std::exception &e){
×
1378
        g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl;
×
1379
        return;
×
1380
      }
×
1381
      try {
×
1382
        vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zoneid);
×
1383
        for(const auto& dr : drs) {
×
1384
          auto lr = getRR<LUARecordContent>(dr.dr);
×
1385
          lua.executeCode(lr->getCode());
×
1386
        }
×
1387
      }
×
1388
      catch(std::exception& e) {
×
1389
        g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<rec<<": "<<e.what()<<endl;
×
1390
      }
×
1391
    });
×
1392
}
×
1393

1394
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)
1395
{
×
1396
  if(!LUA ||                  // we don't have a Lua state yet
×
1397
     !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1398
    LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
×
1399
    setupLuaRecords(*LUA->getLua());
×
1400
  }
×
1401

1402
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1403

1404
  LuaContext& lua = *LUA->getLua();
×
1405

1406
  s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1407
  s_lua_record_ctx->qname = query;
×
1408
  s_lua_record_ctx->zone = zone;
×
1409
  s_lua_record_ctx->zoneid = zoneid;
×
1410

1411
  lua.writeVariable("qname", query);
×
1412
  lua.writeVariable("zone", zone);
×
1413
  lua.writeVariable("zoneid", zoneid);
×
1414
  lua.writeVariable("who", dnsp.getInnerRemote());
×
1415
  lua.writeVariable("localwho", dnsp.getLocal());
×
1416
  lua.writeVariable("dh", (dnsheader*)&dnsp.d);
×
1417
  lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
×
1418
  lua.writeVariable("tcp", dnsp.d_tcp);
×
1419
  lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
×
1420
  if(dnsp.hasEDNSSubnet()) {
×
1421
    lua.writeVariable("ecswho", dnsp.getRealRemote());
×
1422
    s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
×
1423
  }
×
1424
  else {
×
1425
    lua.writeVariable("ecswho", nullptr);
×
1426
    s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
×
1427
  }
×
1428
  lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
×
1429

1430
  try {
×
1431
    string actual;
×
1432
    if(!code.empty() && code[0]!=';')
×
1433
      actual = "return " + code;
×
1434
    else
×
1435
      actual = code.substr(1);
×
1436

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

1439
    vector<string> contents;
×
1440
    if(auto str = boost::get<string>(&content))
×
1441
      contents.push_back(*str);
×
1442
    else
×
1443
      for(const auto& c : boost::get<vector<pair<int,string>>>(content))
×
1444
        contents.push_back(c.second);
×
1445

1446
    for(const auto& content_it: contents) {
×
1447
      if(qtype==QType::TXT)
×
1448
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
×
1449
      else
×
1450
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
×
1451
    }
×
1452
  } catch(std::exception &e) {
×
1453
    g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
×
1454
    try {
×
1455
      std::rethrow_if_nested(e);
×
1456
      g_log<<endl;
×
1457
    } catch(const std::exception& ne) {
×
1458
      g_log << ": " << ne.what() << std::endl;
×
1459
    }
×
1460
    catch(const PDNSException& ne) {
×
1461
      g_log << ": " << ne.reason << std::endl;
×
1462
    }
×
1463
    throw ;
×
1464
  }
×
1465

1466
  return ret;
×
1467
}
×
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

© 2025 Coveralls, Inc