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

PowerDNS / pdns / 10843570514

13 Sep 2024 05:28AM UTC coverage: 61.299% (+5.2%) from 56.093%
10843570514

push

github

web-flow
Merge pull request #14655 from omoerbeek/rec-dot-test-multiple

rec: use multiple forwarding targets for DoT forwarding test

34007 of 88016 branches covered (38.64%)

Branch coverage included in aggregate %.

119284 of 162056 relevant lines covered (73.61%)

4374535.57 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
  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", []() {
×
918
      static string allZerosIP("0.0.0.0");
×
919
      DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
×
920
      // parts is something like ["1", "2", "3", "4", "static"] or
921
      // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
922
      auto parts = rel.getRawLabels();
×
923
      // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
924
      if(parts.size()>=4) {
×
925
        try {
×
926
          ComboAddress ca(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
×
927
          return ca.toString();
×
928
        } catch (const PDNSException &e) {
×
929
          return allZerosIP;
×
930
        }
×
931
      } else if (!parts.empty()) {
×
932
        auto& input = parts.at(0);
×
933

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

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

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

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

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

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

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

1023

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

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

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

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

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

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

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

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

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

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

1106
      auto checker = [](const ComboAddress& addr, const opts_t& opts) {
×
1107
        return g_up.isUp(addr, opts);
×
1108
      };
×
1109
      return genericIfUp(ips, options, checker, port);
×
1110
    });
×
1111

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

1118
      ComboAddress ca_unspec;
×
1119
      ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1120

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

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

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

1145
  lua.writeFunction("ifurlup", [](const std::string& url,
×
1146
                                          const boost::variant<iplist_t, ipunitlist_t>& ips,
×
1147
                                          boost::optional<opts_t> options) {
×
1148

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

1163
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
×
1164
      vector<string> items = convStringList(ips);
×
1165
          return pickRandomSample<string>(n, items);
×
1166
    });
×
1167

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

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

1190
      items.reserve(ips.size());
×
1191
      for (auto& entry : ips) {
×
1192
        items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
×
1193
      }
×
1194

1195
      return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1196
    });
×
1197

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

1206
      items.reserve(ips.size());
×
1207
      for(auto& i : ips)
×
1208
      {
×
1209
        items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
×
1210
      }
×
1211

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

1222
    items.reserve(ips.size());
×
1223
    for (const auto& entry : ips) {
×
1224
      items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
×
1225
    }
×
1226

1227
    return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1228
  });
×
1229

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

1233
      return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1234

1235
    });
×
1236

1237
  if (g_luaRecordExecLimit > 0) {
×
1238
      lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1239
  }
×
1240

1241
  lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){
×
1242
      throw std::runtime_error("Script took too long");
×
1243
    });
×
1244

1245
  lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
×
1246
    return getGeo(ip, attr);
×
1247
  });
×
1248

1249
  typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
×
1250

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

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

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

1336
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
×
1337
      vector<string> result;
×
1338
          result.reserve(ips.size());
×
1339

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

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

1362
      if (!getAuth(rec, qtype, &soaData)) {
×
1363
        return ret;
×
1364
      }
×
1365

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

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

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

1406
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1407

1408
  LuaContext& lua = *LUA->getLua();
×
1409

1410
  s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1411
  s_lua_record_ctx->qname = query;
×
1412
  s_lua_record_ctx->zone = zone;
×
1413
  s_lua_record_ctx->zoneid = zoneid;
×
1414

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

1434
  try {
×
1435
    string actual;
×
1436
    if(!code.empty() && code[0]!=';')
×
1437
      actual = "return " + code;
×
1438
    else
×
1439
      actual = code.substr(1);
×
1440

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

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

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

1470
  return ret;
×
1471
}
×
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