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

PowerDNS / pdns / 12452934727

22 Dec 2024 09:09AM UTC coverage: 64.827% (+3.5%) from 61.37%
12452934727

Pull #14993

github

web-flow
Merge 3a9f09277 into d165e0b05
Pull Request #14993: auth: createForward and createForward6 will use the zone_record as base

37740 of 88984 branches covered (42.41%)

Branch coverage included in aggregate %.

0 of 18 new or added lines in 2 files covered. (0.0%)

81 existing lines in 14 files now uncovered.

126116 of 163773 relevant lines covered (77.01%)

4630603.61 hits per line

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

0.21
/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
    /* first check ? */
75
    std::atomic<bool> first{true};
76
    /* last time the status was accessed */
77
    std::atomic<time_t> lastAccess{0};
78
  };
79

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

577
  return ret;
×
578
}
×
579

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

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

589
  return result;
×
590
}
×
591

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

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

601
  return result;
×
602
}
×
603

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

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

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

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

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

636
  return result;
×
637
}
×
638

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

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

648
  return result;
×
649
}
×
650

651
bool g_LuaRecordSharedState;
652

653
typedef struct AuthLuaRecordContext
654
{
655
  ComboAddress          bestwho;
656
  DNSName               qname;
657
  DNSRecord             zone_record;
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", []() {
×
NEW
919
      static string allZerosIP{"0.0.0.0"};
×
NEW
920
      DNSName record_name{s_lua_record_ctx->zone_record.d_name};
×
NEW
921
      if (record_name.isWildcard() == false) {
×
NEW
922
        return allZerosIP;
×
NEW
923
      }
×
NEW
924
      record_name.chopOff();
×
NEW
925
      DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
926

927
      // parts is something like ["1", "2", "3", "4", "static"] or
928
      // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
929
      auto parts = rel.getRawLabels();
×
930
      // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
931
      if(parts.size()>=4) {
×
932
        try {
×
933
          ComboAddress ca(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
×
934
          return ca.toString();
×
935
        } catch (const PDNSException &e) {
×
936
          return allZerosIP;
×
937
        }
×
938
      } else if (!parts.empty()) {
×
939
        auto& input = parts.at(0);
×
940

941
        // allow a word without - in front, as long as it does not contain anything that could be a number
942
        size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
×
943
        if (nonhexprefix > 0) {
×
944
          input = input.substr(nonhexprefix);
×
945
        }
×
946

947
        // either hex string, or 12-13-14-15
948
        vector<string> ip_parts;
×
949

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

981
  lua.writeFunction("createForward6", []() {
×
NEW
982
      static string allZerosIP{"::"};
×
NEW
983
      DNSName record_name{s_lua_record_ctx->zone_record.d_name};
×
NEW
984
      if (record_name.isWildcard() == false) {
×
NEW
985
        return allZerosIP;
×
NEW
986
      }
×
NEW
987
      record_name.chopOff();
×
NEW
988
      DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
989

990
      auto parts = rel.getRawLabels();
×
991
      if(parts.size()==8) {
×
992
        string tot;
×
993
        for(int i=0; i<8; ++i) {
×
994
          if(i)
×
995
            tot.append(1,':');
×
996
          tot+=parts[i];
×
997
        }
×
998
        ComboAddress ca(tot);
×
999
        return ca.toString();
×
1000
      }
×
1001
      else if(parts.size()==1) {
×
1002
        if (parts[0].find('-') != std::string::npos) {
×
1003
          boost::replace_all(parts[0],"-",":");
×
1004
          ComboAddress ca(parts[0]);
×
1005
          return ca.toString();
×
1006
        } else {
×
1007
          if (parts[0].size() >= 32) {
×
1008
            auto ippart = parts[0].substr(parts[0].size()-32);
×
1009
            auto fulladdress =
×
1010
              ippart.substr(0, 4) + ":" +
×
1011
              ippart.substr(4, 4) + ":" +
×
1012
              ippart.substr(8, 4) + ":" +
×
1013
              ippart.substr(12, 4) + ":" +
×
1014
              ippart.substr(16, 4) + ":" +
×
1015
              ippart.substr(20, 4) + ":" +
×
1016
              ippart.substr(24, 4) + ":" +
×
1017
              ippart.substr(28, 4);
×
1018

1019
            ComboAddress ca(fulladdress);
×
1020
            return ca.toString();
×
1021
          }
×
1022
        }
×
1023
      }
×
1024

NEW
1025
      return allZerosIP;
×
1026
    });
×
1027
  lua.writeFunction("createReverse6", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
1028
      vector<ComboAddress> candidates;
×
1029

1030
      try {
×
1031
        auto labels= s_lua_record_ctx->qname.getRawLabels();
×
1032
        if(labels.size()<32)
×
1033
          return std::string("unknown");
×
1034
        boost::format fmt(format);
×
1035
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
1036

1037

1038
        string together;
×
1039
        vector<string> quads;
×
1040
        for(int i=0; i<8; ++i) {
×
1041
          if(i)
×
1042
            together+=":";
×
1043
          string lquad;
×
1044
          for(int j=0; j <4; ++j) {
×
1045
            lquad.append(1, labels[31-i*4-j][0]);
×
1046
            together += labels[31-i*4-j][0];
×
1047
          }
×
1048
          quads.push_back(lquad);
×
1049
        }
×
1050
        ComboAddress ip6(together,0);
×
1051

1052
        if(e) {
×
1053
          auto& addrs=*e;
×
1054
          for(const auto& addr: addrs) {
×
1055
            // this makes sure we catch all forms of the address
1056
            if(ComboAddress(addr.first,0)==ip6)
×
1057
              return addr.second;
×
1058
          }
×
1059
        }
×
1060

1061
        string dashed=ip6.toString();
×
1062
        boost::replace_all(dashed, ":", "-");
×
1063

1064
        for(int i=31; i>=0; --i)
×
1065
          fmt % labels[i];
×
1066
        fmt % dashed;
×
1067

1068
        for(const auto& lquad : quads)
×
1069
          fmt % lquad;
×
1070

1071
        return fmt.str();
×
1072
      }
×
1073
      catch(std::exception& ex) {
×
1074
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.what()<<endl;
×
1075
      }
×
1076
      catch(PDNSException& ex) {
×
1077
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl;
×
1078
      }
×
1079
      return std::string("unknown");
×
1080
    });
×
1081

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

1085
      if (nmg.match(ComboAddress(address))) {
×
1086
        return {address};
×
1087
      } else {
×
1088
        if (fallback) {
×
1089
          if (fallback->empty()) {
×
1090
            // if fallback is an empty string, return an empty array
1091
            return {};
×
1092
          }
×
1093
          return {*fallback};
×
1094
        }
×
1095

1096
        if (ca.isIPv4()) {
×
1097
          return {string("0.0.0.0")};
×
1098
        } else {
×
1099
          return {string("::")};
×
1100
        }
×
1101
      }
×
1102
    });
×
1103

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

1120
      auto checker = [](const ComboAddress& addr, const opts_t& opts) {
×
1121
        return g_up.isUp(addr, opts);
×
1122
      };
×
1123
      return genericIfUp(ips, options, checker, port);
×
1124
    });
×
1125

1126
  lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) {
×
1127
      vector<ComboAddress> candidates;
×
1128
      opts_t opts;
×
1129
      if(options)
×
1130
        opts = *options;
×
1131

1132
      ComboAddress ca_unspec;
×
1133
      ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1134

1135
      // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1136
      for (const auto& [count, unitmap] : ipurls) {
×
1137
        // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1138
        vector<ComboAddress> available;
×
1139

1140
        for (const auto& [ipStr, url] : unitmap) {
×
1141
          // unit: ["192.0.2.1"] = "https://example.com"
1142
          ComboAddress ip(ipStr);
×
1143
          candidates.push_back(ip);
×
1144
          if (g_up.isUp(ca_unspec, url, opts)) {
×
1145
            available.push_back(ip);
×
1146
          }
×
1147
        }
×
1148
        if(!available.empty()) {
×
1149
          vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
1150
          return convComboAddressListToString(res);
×
1151
        }
×
1152
      }
×
1153

1154
      // All units down, apply backupSelector on all candidates
1155
      vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
×
1156
      return convComboAddressListToString(res);
×
1157
    });
×
1158

1159
  lua.writeFunction("ifurlup", [](const std::string& url,
×
1160
                                          const boost::variant<iplist_t, ipunitlist_t>& ips,
×
1161
                                          boost::optional<opts_t> options) {
×
1162

1163
    auto checker = [&url](const ComboAddress& addr, const opts_t& opts) {
×
1164
        return g_up.isUp(addr, url, opts);
×
1165
      };
×
1166
      return genericIfUp(ips, options, checker);
×
1167
    });
×
1168
  /*
1169
   * Returns a random IP address from the supplied list
1170
   * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
1171
   */
1172
  lua.writeFunction("pickrandom", [](const iplist_t& ips) {
×
1173
      vector<string> items = convStringList(ips);
×
1174
      return pickRandom<string>(items);
×
1175
    });
×
1176

1177
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
×
1178
      vector<string> items = convStringList(ips);
×
1179
          return pickRandomSample<string>(n, items);
×
1180
    });
×
1181

1182
  lua.writeFunction("pickhashed", [](const iplist_t& ips) {
×
1183
      vector<string> items = convStringList(ips);
×
1184
      return pickHashed<string>(s_lua_record_ctx->bestwho, items);
×
1185
    });
×
1186
  /*
1187
   * Returns a random IP address from the supplied list, as weighted by the
1188
   * various ``weight`` parameters
1189
   * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1190
   */
1191
  lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
×
1192
      vector< pair<int, string> > items = convIntStringPairList(ips);
×
1193
      return pickWeightedRandom<string>(items);
×
1194
    });
×
1195

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

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

1209
      return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1210
    });
×
1211

1212
  /*
1213
   * Based on the hash of the record name, return an IP address from the list
1214
   * supplied, as weighted by the various `weight` parameters
1215
   * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1216
   */
1217
  lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t > ips) {
×
1218
      vector< pair<int, string> > items;
×
1219

1220
      items.reserve(ips.size());
×
1221
      for(auto& i : ips)
×
1222
      {
×
1223
        items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
×
1224
      }
×
1225

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

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

1241
    return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1242
  });
×
1243

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

1247
      return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1248

1249
    });
×
1250

1251
  if (g_luaRecordExecLimit > 0) {
×
1252
      lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1253
  }
×
1254

1255
  lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){
×
1256
      throw std::runtime_error("Script took too long");
×
1257
    });
×
1258

1259
  lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
×
1260
    return getGeo(ip, attr);
×
1261
  });
×
1262

1263
  typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
×
1264

1265
  lua.writeFunction("asnum", [](const combovar_t& asns) {
×
1266
      string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
×
1267
      return doCompare(asns, res, [](const std::string& a, const std::string& b) {
×
1268
          return !strcasecmp(a.c_str(), b.c_str());
×
1269
        });
×
1270
    });
×
1271
  lua.writeFunction("continent", [](const combovar_t& continent) {
×
1272
     string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1273
      return doCompare(continent, res, [](const std::string& a, const std::string& b) {
×
1274
          return !strcasecmp(a.c_str(), b.c_str());
×
1275
        });
×
1276
    });
×
1277
  lua.writeFunction("continentCode", []() {
×
1278
      string unknown("unknown");
×
1279
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1280
      if ( res == unknown ) {
×
1281
       return std::string("--");
×
1282
      }
×
1283
      return res;
×
1284
    });
×
1285
  lua.writeFunction("country", [](const combovar_t& var) {
×
1286
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1287
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1288
          return !strcasecmp(a.c_str(), b.c_str());
×
1289
        });
×
1290

1291
    });
×
1292
  lua.writeFunction("countryCode", []() {
×
1293
      string unknown("unknown");
×
1294
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1295
      if ( res == unknown ) {
×
1296
       return std::string("--");
×
1297
      }
×
1298
      return res;
×
1299
    });
×
1300
  lua.writeFunction("region", [](const combovar_t& var) {
×
1301
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1302
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1303
          return !strcasecmp(a.c_str(), b.c_str());
×
1304
        });
×
1305

1306
    });
×
1307
  lua.writeFunction("regionCode", []() {
×
1308
      string unknown("unknown");
×
1309
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1310
      if ( res == unknown ) {
×
1311
       return std::string("--");
×
1312
      }
×
1313
      return res;
×
1314
    });
×
1315
  lua.writeFunction("netmask", [](const iplist_t& ips) {
×
1316
      for(const auto& i :ips) {
×
1317
        Netmask nm(i.second);
×
1318
        if(nm.match(s_lua_record_ctx->bestwho))
×
1319
          return true;
×
1320
      }
×
1321
      return false;
×
1322
    });
×
1323
  /* {
1324
       {
1325
        {'192.168.0.0/16', '10.0.0.0/8'},
1326
        {'192.168.20.20', '192.168.20.21'}
1327
       },
1328
       {
1329
        {'0.0.0.0/0'}, {'192.0.2.1'}
1330
       }
1331
     }
1332
  */
1333
  lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) {
×
1334
      for(const auto& rule : in) {
×
1335
        const auto& netmasks=rule.second[0].second;
×
1336
        const auto& destinations=rule.second[1].second;
×
1337
        for(const auto& nmpair : netmasks) {
×
1338
          Netmask nm(nmpair.second);
×
1339
          if(nm.match(s_lua_record_ctx->bestwho)) {
×
1340
            if (destinations.empty()) {
×
1341
              throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm.toString() + ")");
×
1342
            }
×
1343
            return destinations[dns_random(destinations.size())].second;
×
1344
          }
×
1345
        }
×
1346
      }
×
1347
      return std::string();
×
1348
    });
×
1349

1350
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
×
1351
      vector<string> result;
×
1352
          result.reserve(ips.size());
×
1353

1354
      for(const auto& ip : ips) {
×
1355
          result.emplace_back(ip.second);
×
1356
      }
×
1357
      if(result.empty()) {
×
1358
        throw std::invalid_argument("The IP list cannot be empty");
×
1359
      }
×
1360
      return result;
×
1361
    });
×
1362

1363
  lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) {
×
1364
    DNSName rec;
×
1365
    vector<string> ret;
×
1366
    try {
×
1367
      rec = DNSName(record);
×
1368
    }
×
1369
    catch (const std::exception& e) {
×
1370
      g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
×
1371
      return ret;
×
1372
    }
×
1373
    try {
×
1374
      SOAData soaData;
×
1375

1376
      if (!getAuth(rec, qtype, &soaData)) {
×
1377
        return ret;
×
1378
      }
×
1379

1380
      vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id);
×
1381
      for (const auto& drec : drs) {
×
1382
        ret.push_back(drec.dr.getContent()->getZoneRepresentation());
×
1383
      }
×
1384
    }
×
1385
    catch (std::exception& e) {
×
1386
      g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
×
1387
    }
×
1388
    return ret;
×
1389
  });
×
1390

1391
  lua.writeFunction("include", [&lua](string record) {
×
1392
      DNSName rec;
×
1393
      try {
×
1394
        rec = DNSName(record) + s_lua_record_ctx->zone;
×
1395
      } catch (const std::exception &e){
×
1396
        g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl;
×
1397
        return;
×
1398
      }
×
1399
      try {
×
1400
        vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zoneid);
×
1401
        for(const auto& dr : drs) {
×
1402
          auto lr = getRR<LUARecordContent>(dr.dr);
×
1403
          lua.executeCode(lr->getCode());
×
1404
        }
×
1405
      }
×
1406
      catch(std::exception& e) {
×
1407
        g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<rec<<": "<<e.what()<<endl;
×
1408
      }
×
1409
    });
×
1410
}
×
1411

1412
std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSRecord& zone_record, const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype, unique_ptr<AuthLua4>& LUA)
1413
{
×
1414
  if(!LUA ||                  // we don't have a Lua state yet
×
1415
     !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1416
    LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
×
1417
    setupLuaRecords(*LUA->getLua());
×
1418
  }
×
1419

1420
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1421

1422
  LuaContext& lua = *LUA->getLua();
×
1423

1424
  s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1425
  s_lua_record_ctx->qname = query;
×
NEW
1426
  s_lua_record_ctx->zone_record = zone_record;
×
1427
  s_lua_record_ctx->zone = zone;
×
1428
  s_lua_record_ctx->zoneid = zoneid;
×
1429

1430
  lua.writeVariable("qname", query);
×
1431
  lua.writeVariable("zone", zone);
×
1432
  lua.writeVariable("zoneid", zoneid);
×
1433
  lua.writeVariable("who", dnsp.getInnerRemote());
×
1434
  lua.writeVariable("localwho", dnsp.getLocal());
×
1435
  lua.writeVariable("dh", (dnsheader*)&dnsp.d);
×
1436
  lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
×
1437
  lua.writeVariable("tcp", dnsp.d_tcp);
×
1438
  lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
×
1439
  if(dnsp.hasEDNSSubnet()) {
×
1440
    lua.writeVariable("ecswho", dnsp.getRealRemote());
×
1441
    s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
×
1442
  }
×
1443
  else {
×
1444
    lua.writeVariable("ecswho", nullptr);
×
1445
    s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
×
1446
  }
×
1447
  lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
×
1448

1449
  try {
×
1450
    string actual;
×
1451
    if(!code.empty() && code[0]!=';')
×
1452
      actual = "return " + code;
×
1453
    else
×
1454
      actual = code.substr(1);
×
1455

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

1458
    vector<string> contents;
×
1459
    if(auto str = boost::get<string>(&content))
×
1460
      contents.push_back(*str);
×
1461
    else
×
1462
      for(const auto& c : boost::get<vector<pair<int,string>>>(content))
×
1463
        contents.push_back(c.second);
×
1464

1465
    for(const auto& content_it: contents) {
×
1466
      if(qtype==QType::TXT)
×
1467
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
×
1468
      else
×
1469
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
×
1470
    }
×
1471
  } catch(std::exception &e) {
×
1472
    g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
×
1473
    try {
×
1474
      std::rethrow_if_nested(e);
×
1475
      g_log<<endl;
×
1476
    } catch(const std::exception& ne) {
×
1477
      g_log << ": " << ne.what() << std::endl;
×
1478
    }
×
1479
    catch(const PDNSException& ne) {
×
1480
      g_log << ": " << ne.reason << std::endl;
×
1481
    }
×
1482
    throw ;
×
1483
  }
×
1484

1485
  return ret;
×
1486
}
×
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