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

PowerDNS / pdns / 12633166134

06 Jan 2025 01:13PM UTC coverage: 64.847% (+3.5%) from 61.37%
12633166134

Pull #14993

github

web-flow
Merge 7c62e29c0 into 60cfbeada
Pull Request #14993: auth: createForward and createForward6 will use the zone_record as base

37782 of 89010 branches covered (42.45%)

Branch coverage included in aggregate %.

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

228 existing lines in 19 files now uncovered.

126169 of 163818 relevant lines covered (77.02%)

4825199.24 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
  DNSZoneRecord         zone_record;
658
  DNSName               zone;
659
} lua_record_ctx_t;
660

661
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
662

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

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

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

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

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

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

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

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

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

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

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

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

763
  return result;
×
764
}
×
765

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

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

776
  cleanZoneHashes();
×
777

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

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

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

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

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

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

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

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

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

880

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

887
        vector<ComboAddress> candidates;
×
888

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

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

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

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

910
        return fmt.str();
×
911
      }
×
912
      catch(std::exception& ex) {
×
913
        g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
×
914
      }
×
915
      return std::string("error");
×
916
    });
×
917
  lua.writeFunction("createForward", []() {
×
NEW
918
      static string allZerosIP{"0.0.0.0"};
×
NEW
919
      DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
NEW
920
      if (!record_name.isWildcard()) {
×
NEW
921
        return allZerosIP;
×
NEW
922
      }
×
NEW
923
      record_name.chopOff();
×
NEW
924
      DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
925

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

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

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

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

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

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

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

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

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

1036

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1248
    });
×
1249

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1483
  return ret;
×
1484
}
×
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