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

PowerDNS / pdns / 13590498558

28 Feb 2025 02:46PM UTC coverage: 63.153% (+0.06%) from 63.098%
13590498558

push

github

web-flow
Merge pull request #15228 from miodvallat/backport-15222-to-auth-4.9.x

auth 4.9.x: Backport "Damage control in Lua createForward() and createForward6()."

12979 of 27286 branches covered (47.57%)

Branch coverage included in aggregate %.

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

17 existing lines in 3 files now uncovered.

41776 of 59416 relevant lines covered (70.31%)

5599505.94 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
  {
153✔
83
    d_checkerThreadStarted.clear();
153✔
84
  }
153✔
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
  DNSName               zone;
658
  int                   zoneid;
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
{
×
768
  const auto& zoneId = s_lua_record_ctx->zoneid;
×
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
      try {
×
NEW
920
        DNSName rel{s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone)};
×
921

922
        // parts is something like ["1", "2", "3", "4", "static"] or
923
        // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
NEW
924
        auto parts = rel.getRawLabels();
×
925
        // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
NEW
926
        if (parts.size() >= 4) {
×
NEW
927
          ComboAddress address(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
×
NEW
928
          return address.toString();
×
929
        }
×
NEW
930
        if (!parts.empty()) {
×
NEW
931
          auto& input = parts.at(0);
×
932

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

939
          // either hex string, or 12-13-14-15
NEW
940
          vector<string> ip_parts;
×
941

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

976
  lua.writeFunction("createForward6", []() {
×
NEW
977
      static string allZerosIP{"::"};
×
NEW
978
      try {
×
NEW
979
        DNSName rel{s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone)};
×
980

NEW
981
        auto parts = rel.getRawLabels();
×
NEW
982
        if (parts.size() == 8) {
×
NEW
983
          string tot;
×
NEW
984
          for (int chunk = 0; chunk < 8; ++chunk) {
×
NEW
985
            if (chunk != 0) {
×
NEW
986
              tot.append(1, ':');
×
NEW
987
              }
×
NEW
988
            tot += parts.at(chunk);
×
NEW
989
          }
×
NEW
990
          ComboAddress address(tot);
×
NEW
991
          return address.toString();
×
992
        }
×
NEW
993
        if (parts.size() == 1) {
×
NEW
994
          if (parts[0].find('-') != std::string::npos) {
×
NEW
995
            std::replace(parts[0].begin(), parts[0].end(), '-', ':');
×
NEW
996
            ComboAddress address(parts[0]);
×
NEW
997
            return address.toString();
×
NEW
998
          }
×
999
          if (parts[0].size() >= 32) {
×
1000
            auto ippart = parts[0].substr(parts[0].size()-32);
×
1001
            auto fulladdress =
×
1002
              ippart.substr(0, 4) + ":" +
×
1003
              ippart.substr(4, 4) + ":" +
×
1004
              ippart.substr(8, 4) + ":" +
×
1005
              ippart.substr(12, 4) + ":" +
×
1006
              ippart.substr(16, 4) + ":" +
×
1007
              ippart.substr(20, 4) + ":" +
×
1008
              ippart.substr(24, 4) + ":" +
×
1009
              ippart.substr(28, 4);
×
1010

NEW
1011
            ComboAddress address(fulladdress);
×
NEW
1012
            return address.toString();
×
1013
          }
×
1014
        }
×
NEW
1015
        return allZerosIP;
×
NEW
1016
      } catch (const PDNSException &e) {
×
NEW
1017
        return allZerosIP;
×
UNCOV
1018
      }
×
UNCOV
1019
    });
×
NEW
1020
  lua.writeFunction("createReverse6", [](const string &format, boost::optional<std::unordered_map<string,string>> excp){
×
1021
      vector<ComboAddress> candidates;
×
1022

1023
      try {
×
1024
        auto labels= s_lua_record_ctx->qname.getRawLabels();
×
NEW
1025
        if (labels.size()<32) {
×
1026
          return std::string("unknown");
×
NEW
1027
        }
×
1028
        boost::format fmt(format);
×
1029
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
1030

1031

1032
        string together;
×
1033
        vector<string> quads;
×
NEW
1034
        for (int chunk = 0; chunk < 8; ++chunk) {
×
NEW
1035
          if (chunk != 0) {
×
NEW
1036
            together += ":";
×
NEW
1037
          }
×
1038
          string lquad;
×
NEW
1039
          for (int quartet = 0; quartet < 4; ++quartet) {
×
NEW
1040
            lquad.append(1, labels[31 - chunk * 4 - quartet][0]);
×
NEW
1041
            together += labels[31 - chunk * 4 - quartet][0];
×
1042
          }
×
1043
          quads.push_back(lquad);
×
1044
        }
×
NEW
1045
        ComboAddress ip6(together,0);
×
1046

NEW
1047
        if (excp) {
×
NEW
1048
          auto& addrs=*excp;
×
UNCOV
1049
          for(const auto& addr: addrs) {
×
1050
            // this makes sure we catch all forms of the address
NEW
1051
            if (ComboAddress(addr.first, 0) == ip6) {
×
1052
              return addr.second;
×
NEW
1053
            }
×
1054
          }
×
1055
        }
×
1056

1057
        string dashed=ip6.toString();
×
1058
        boost::replace_all(dashed, ":", "-");
×
1059

NEW
1060
        for (int byte = 31; byte >= 0; --byte) {
×
NEW
1061
          fmt % labels[byte];
×
NEW
1062
        }
×
UNCOV
1063
        fmt % dashed;
×
1064

NEW
1065
        for(const auto& lquad : quads) {
×
1066
          fmt % lquad;
×
NEW
1067
        }
×
1068

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1247
    });
×
1248

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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