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

PowerDNS / pdns / 8246836297

12 Mar 2024 09:56AM UTC coverage: 53.746% (-5.4%) from 59.178%
8246836297

push

github

web-flow
Merge pull request #13879 from Habbie/auth-lua-filterforward-empty

auth LUA: support returning empty set in filterForward

23464 of 68850 branches covered (34.08%)

Branch coverage included in aggregate %.

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

12574 existing lines in 133 files now uncovered.

89131 of 140646 relevant lines covered (63.37%)

3409522.02 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
  {
102✔
83
    d_checkerThreadStarted.clear();
102✔
84
  }
102✔
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
    string remstring;
×
94
    try {
×
95
      int timeout = 2;
×
96
      if (cd.opts.count("timeout")) {
×
97
        timeout = std::atoi(cd.opts.at("timeout").c_str());
×
98
      }
×
99
      string useragent = productName();
×
100
      if (cd.opts.count("useragent")) {
×
101
        useragent = cd.opts.at("useragent");
×
102
      }
×
103
      size_t byteslimit = 0;
×
104
      if (cd.opts.count("byteslimit")) {
×
105
        byteslimit = static_cast<size_t>(std::atoi(cd.opts.at("byteslimit").c_str()));
×
106
      }
×
107
      MiniCurl mc(useragent);
×
108

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

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

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

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

207
  typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
208
  SharedLockGuarded<statuses_t> d_statuses;
209

210
  std::unique_ptr<std::thread> d_checkerThread;
211
  std::atomic_flag d_checkerThreadStarted;
212

213
  void setStatus(const CheckDesc& cd, bool status)
214
  {
×
215
    auto statuses = d_statuses.write_lock();
×
216
    auto& state = (*statuses)[cd];
×
217
    state->status = status;
×
218
    if (state->first) {
×
219
      state->first = false;
×
220
    }
×
221
  }
×
222

223
  void setDown(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
224
  {
×
225
    CheckDesc cd{rem, url, opts};
×
226
    setStatus(cd, false);
×
227
  }
×
228

229
  void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
230
  {
×
231
    CheckDesc cd{rem, url, opts};
×
232

×
233
    setStatus(cd, true);
×
234
  }
×
235

236
  void setDown(const CheckDesc& cd)
237
  {
×
238
    setStatus(cd, false);
×
239
  }
×
240

241
  void setUp(const CheckDesc& cd)
242
  {
×
243
    setStatus(cd, true);
×
244
  }
×
245
};
246

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

275
bool IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts)
276
{
×
277
  CheckDesc cd{remote, "", opts};
×
278
  return isUp(cd);
×
279
}
×
280

281
bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts)
282
{
×
283
  CheckDesc cd{remote, url, opts};
×
284
  return isUp(cd);
×
285
}
×
286

287
IsUpOracle g_up;
288
namespace {
289
template<typename T, typename C>
290
bool doCompare(const T& var, const std::string& res, const C& cmp)
291
{
×
292
  if(auto country = boost::get<string>(&var))
×
293
    return cmp(*country, res);
×
294

295
  auto countries=boost::get<vector<pair<int,string> > >(&var);
×
296
  for(const auto& country : *countries) {
×
297
    if(cmp(country.second, res))
×
298
      return true;
×
299
  }
×
300
  return false;
×
301
}
×
302
}
303

304
static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
305
{
×
306
  static bool initialized;
×
307
  extern std::function<std::string(const std::string& ip, int)> g_getGeo;
×
308
  if(!g_getGeo) {
×
309
    if(!initialized) {
×
310
      g_log<<Logger::Error<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl;
×
311
      initialized=true;
×
312
    }
×
313
    return "unknown";
×
314
  }
×
315
  else
×
316
    return g_getGeo(ip, (int)qa);
×
317
}
×
318

319
template <typename T>
320
static T pickRandom(const vector<T>& items)
321
{
×
322
  if (items.empty()) {
×
323
    throw std::invalid_argument("The items list cannot be empty");
×
324
  }
×
325
  return items[dns_random(items.size())];
×
326
}
×
327

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

338
template <typename T>
339
static T pickWeightedRandom(const vector< pair<int, T> >& items)
340
{
×
341
  if (items.empty()) {
×
342
    throw std::invalid_argument("The items list cannot be empty");
×
343
  }
×
344
  int sum=0;
×
345
  vector< pair<int, T> > pick;
×
346
  pick.reserve(items.size());
×
347

348
  for(auto& i : items) {
×
349
    sum += i.first;
×
350
    pick.emplace_back(sum, i.second);
×
351
  }
×
352

353
  if (sum == 0) {
×
354
    throw std::invalid_argument("The sum of items cannot be zero");
×
355
  }
×
356

357
  int r = dns_random(sum);
×
358
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
359
  return p->second;
×
360
}
×
361

362
template <typename T>
363
static T pickWeightedHashed(const ComboAddress& bestwho, const vector< pair<int, T> >& items)
364
{
×
365
  if (items.empty()) {
×
366
    throw std::invalid_argument("The items list cannot be empty");
×
367
  }
×
368
  int sum=0;
×
369
  vector< pair<int, T> > pick;
×
370
  pick.reserve(items.size());
×
371

372
  for(auto& i : items) {
×
373
    sum += i.first;
×
374
    pick.push_back({sum, i.second});
×
375
  }
×
376

377
  if (sum == 0) {
×
378
    throw std::invalid_argument("The sum of items cannot be zero");
×
379
  }
×
380

381
  ComboAddress::addressOnlyHash aoh;
×
382
  int r = aoh(bestwho) % sum;
×
383
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
384
  return p->second;
×
385
}
×
386

387
template <typename T>
388
static T pickWeightedNameHashed(const DNSName& dnsname, vector< pair<int, T> >& items)
389
{
×
390
  if (items.empty()) {
×
391
    throw std::invalid_argument("The items list cannot be empty");
×
392
  }
×
393
  size_t sum=0;
×
394
  vector< pair<int, T> > pick;
×
395
  pick.reserve(items.size());
×
396

397
  for(auto& i : items) {
×
398
    sum += i.first;
×
399
    pick.push_back({sum, i.second});
×
400
  }
×
401

402
  if (sum == 0) {
×
403
    throw std::invalid_argument("The sum of items cannot be zero");
×
404
  }
×
405

406
  size_t r = dnsname.hash() % sum;
×
407
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
408
  return p->second;
×
409
}
×
410

411
template <typename T>
412
static vector<T> pickRandomSample(int n, const vector<T>& items)
413
{
×
414
  if (items.empty()) {
×
415
    throw std::invalid_argument("The items list cannot be empty");
×
416
  }
×
417

418
  vector<T> pick;
×
419
  pick.reserve(items.size());
×
420

421
  for(auto& item : items) {
×
422
    pick.push_back(item);
×
423
  }
×
424

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

427
  if (count == 0) {
×
428
    return vector<T>();
×
429
  }
×
430

431
  std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
×
432

433
  vector<T> result = {pick.begin(), pick.begin() + count};
×
434
  return result;
×
435
}
×
436

437
static bool getLatLon(const std::string& ip, double& lat, double& lon)
438
{
×
439
  string inp = getGeo(ip, GeoIPInterface::Location);
×
440
  if(inp.empty())
×
441
    return false;
×
442
  lat=atof(inp.c_str());
×
443
  auto pos=inp.find(' ');
×
444
  if(pos != string::npos)
×
445
    lon=atof(inp.c_str() + pos);
×
446
  return true;
×
447
}
×
448

449
static bool getLatLon(const std::string& ip, string& loc)
450
{
×
451
  int latdeg, latmin, londeg, lonmin;
×
452
  double latsec, lonsec;
×
453
  char lathem='X', lonhem='X';
×
454

455
  double lat = 0, lon = 0;
×
456
  if(!getLatLon(ip, lat, lon))
×
457
    return false;
×
458

459
  if(lat > 0) {
×
460
    lathem='N';
×
461
  }
×
462
  else {
×
463
    lat = -lat;
×
464
    lathem='S';
×
465
  }
×
466

467
  if(lon > 0) {
×
468
    lonhem='E';
×
469
  }
×
470
  else {
×
471
    lon = -lon;
×
472
    lonhem='W';
×
473
  }
×
474

475
  latdeg = lat;
×
476
  latmin = (lat - latdeg)*60.0;
×
477
  latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
×
478

479
  londeg = lon;
×
480
  lonmin = (lon - londeg)*60.0;
×
481
  lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
×
482

483
  // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
484

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

487
  loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
×
488
  return true;
×
489
}
×
490

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

516
static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid)
517
{
×
518
  static LockGuarded<UeberBackend> s_ub;
×
519

520
  DNSZoneRecord dr;
×
521
  vector<DNSZoneRecord> ret;
×
522
  {
×
523
    auto ub = s_ub.lock();
×
524
    ub->lookup(QType(qtype), name, zoneid);
×
525
    while (ub->get(dr)) {
×
526
      ret.push_back(dr);
×
527
    }
×
528
  }
×
529
  return ret;
×
530
}
×
531

532
static bool getAuth(const DNSName& name, uint16_t qtype, SOAData* soaData)
533
{
×
534
  static LockGuarded<UeberBackend> s_ub;
×
535

536
  {
×
537
    auto ueback = s_ub.lock();
×
538
    return ueback->getAuth(name, qtype, soaData);
×
539
  }
×
540
}
×
541

542
static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
543
{
×
544
  string selector=defaultValue;
×
545
  if(options) {
×
546
    if(options->count(name))
×
547
      selector=options->find(name)->second;
×
548
  }
×
549
  return selector;
×
550
}
×
551

552
static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
553
{
×
554
  vector<ComboAddress> ret;
×
555

556
  if(selector=="all")
×
557
    return candidates;
×
558
  else if(selector=="random")
×
559
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
560
  else if(selector=="pickclosest")
×
561
    ret.emplace_back(pickclosest(bestwho, candidates));
×
562
  else if(selector=="hashed")
×
563
    ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates));
×
564
  else {
×
565
    g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl;
×
566
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
567
  }
×
568

569
  return ret;
×
570
}
×
571

572
static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
573
{
×
574
  vector<string> result;
×
575
  result.reserve(items.size());
×
576

577
  for (const auto& item : items) {
×
578
    result.emplace_back(item.toString());
×
579
  }
×
580

581
  return result;
×
582
}
×
583

584
static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
585
{
×
586
  vector<ComboAddress> result;
×
587
  result.reserve(items.size());
×
588

589
  for(const auto& item : items) {
×
590
    result.emplace_back(ComboAddress(item.second, port));
×
591
  }
×
592

593
  return result;
×
594
}
×
595

596
/**
597
 * Reads and unify single or multiple sets of ips :
598
 * - {'192.0.2.1', '192.0.2.2'}
599
 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
600
 */
601

602
static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
603
{
×
604
  vector<vector<ComboAddress>> candidates;
×
605

606
  if(auto simple = boost::get<iplist_t>(&items)) {
×
607
    vector<ComboAddress> unit = convComboAddressList(*simple, port);
×
608
    candidates.push_back(unit);
×
609
  } else {
×
610
    auto units = boost::get<ipunitlist_t>(items);
×
611
    for(const auto& u : units) {
×
612
      vector<ComboAddress> unit = convComboAddressList(u.second, port);
×
613
      candidates.push_back(unit);
×
614
    }
×
615
  }
×
616
  return candidates;
×
617
}
×
618

619
static vector<string> convStringList(const iplist_t& items)
620
{
×
621
  vector<string> result;
×
622
  result.reserve(items.size());
×
623

624
  for(const auto& item : items) {
×
625
    result.emplace_back(item.second);
×
626
  }
×
627

628
  return result;
×
629
}
×
630

631
static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
632
{
×
633
  vector<pair<int,string> > result;
×
634
  result.reserve(items.size());
×
635

636
  for(const auto& item : items) {
×
637
    result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
×
638
  }
×
639

640
  return result;
×
641
}
×
642

643
bool g_LuaRecordSharedState;
644

645
typedef struct AuthLuaRecordContext
646
{
647
  ComboAddress          bestwho;
648
  DNSName               qname;
649
  DNSName               zone;
650
  int                   zoneid;
651
} lua_record_ctx_t;
652

653
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
654

655
/*
656
 *  Holds computed hashes for a given entry
657
 */
658
struct EntryHashesHolder
659
{
660
  std::atomic<size_t> weight;
661
  std::string entry;
662
  SharedLockGuarded<std::vector<unsigned int>> hashes;
663
  std::atomic<time_t> lastUsed;
664

665
  EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
×
666
  }
×
667

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

687
using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
688

689
static SharedLockGuarded<std::map<
690
  zone_hashes_key_t, // zoneid qname entry
691
  std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
692
  >>
693
s_zone_hashes;
694

695
static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
696

697
/**
698
 * every ~g_luaConsistentHashesCleanupInterval, do a cleanup to delete entries that haven't been used in the last g_luaConsistentHashesExpireDelay
699
 */
700
static void cleanZoneHashes()
701
{
×
702
  auto now = time(nullptr);
×
703
  if (s_lastConsistentHashesCleanup > (now - g_luaConsistentHashesCleanupInterval)) {
×
704
    return ;
×
705
  }
×
706
  s_lastConsistentHashesCleanup = now;
×
707
  std::vector<zone_hashes_key_t> toDelete{};
×
708
  {
×
709
    auto locked = s_zone_hashes.read_lock();
×
710
    auto someTimeAgo = now - g_luaConsistentHashesExpireDelay;
×
711

712
    for (const auto& [key, entry]: *locked) {
×
713
      if (entry->lastUsed > someTimeAgo) {
×
714
        toDelete.push_back(key);
×
715
      }
×
716
    }
×
717
  }
×
718
  if (!toDelete.empty()) {
×
719
    auto wlocked = s_zone_hashes.write_lock();
×
720
    for (const auto& key : toDelete) {
×
721
      wlocked->erase(key);
×
722
    }
×
723
  }
×
724
}
×
725

726
static std::vector<std::shared_ptr<EntryHashesHolder>> getCHashedEntries(const int zoneId, const std::string& queryName, const std::vector<std::pair<int, std::string>>& items)
727
{
×
728
  std::vector<std::shared_ptr<EntryHashesHolder>> result{};
×
729
  std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
×
730

731
  {
×
732
    time_t now = time(nullptr);
×
733
    auto locked = s_zone_hashes.read_lock();
×
734

735
    for (const auto& [weight, entry]: items) {
×
736
      auto key = std::make_tuple(zoneId, queryName, entry);
×
737
      if (locked->count(key) == 0) {
×
738
        newEntries[key] = std::make_shared<EntryHashesHolder>(weight, entry, now);
×
739
      } else {
×
740
        locked->at(key)->weight = weight;
×
741
        locked->at(key)->lastUsed = now;
×
742
        result.push_back(locked->at(key));
×
743
      }
×
744
    }
×
745
  }
×
746
  if (!newEntries.empty()) {
×
747
    auto wlocked = s_zone_hashes.write_lock();
×
748

749
    for (auto& [key, entry]: newEntries) {
×
750
      result.push_back(entry);
×
751
      (*wlocked)[key] = std::move(entry);
×
752
    }
×
753
  }
×
754

755
  return result;
×
756
}
×
757

758
static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
759
{
×
760
  const auto& zoneId = s_lua_record_ctx->zoneid;
×
761
  const auto queryName = s_lua_record_ctx->qname.toString();
×
762
  unsigned int sel = std::numeric_limits<unsigned int>::max();
×
763
  unsigned int min = std::numeric_limits<unsigned int>::max();
×
764

765
  boost::optional<std::string> ret;
×
766
  boost::optional<std::string> first;
×
767

768
  cleanZoneHashes();
×
769

770
  auto entries = getCHashedEntries(zoneId, queryName, items);
×
771

772
  ComboAddress::addressOnlyHash addrOnlyHash;
×
773
  auto qhash = addrOnlyHash(bestwho);
×
774
  for (const auto& entry : entries) {
×
775
    if (!entry->hashesComputed()) {
×
776
      entry->hash();
×
777
    }
×
778
    {
×
779
      const auto hashes = entry->hashes.read_lock();
×
780
      if (!hashes->empty()) {
×
781
        if (min > *(hashes->begin())) {
×
782
          min = *(hashes->begin());
×
783
          first = entry->entry;
×
784
        }
×
785

786
        auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
×
787
        if (hash_it != hashes->end()) {
×
788
          if (*hash_it < sel) {
×
789
            sel = *hash_it;
×
790
            ret = entry->entry;
×
791
          }
×
792
        }
×
793
      }
×
794
    }
×
795
  }
×
796
  if (ret != boost::none) {
×
797
    return *ret;
×
798
  }
×
799
  if (first != boost::none) {
×
800
    return *first;
×
801
  }
×
802
  return {};
×
803
}
×
804

805
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)
806
{
×
807
  vector<vector<ComboAddress> > candidates;
×
808
  opts_t opts;
×
809
  if(options)
×
810
    opts = *options;
×
811

812
  candidates = convMultiComboAddressList(ips, port);
×
813

814
  for(const auto& unit : candidates) {
×
815
    vector<ComboAddress> available;
×
816
    for(const auto& c : unit) {
×
817
      if (upcheckf(c, opts)) {
×
818
        available.push_back(c);
×
819
      }
×
820
    }
×
821
    if(!available.empty()) {
×
822
      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
823
      return convComboAddressListToString(res);
×
824
    }
×
825
  }
×
826

827
  // All units down, apply backupSelector on all candidates
828
  vector<ComboAddress> ret{};
×
829
  for(const auto& unit : candidates) {
×
830
    ret.insert(ret.end(), unit.begin(), unit.end());
×
831
  }
×
832

833
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
×
834
  return convComboAddressListToString(res);
×
835
}
×
836

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

872

873
  lua.writeFunction("createReverse", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
874
      try {
×
875
        auto labels = s_lua_record_ctx->qname.getRawLabels();
×
876
        if(labels.size()<4)
×
877
          return std::string("unknown");
×
878

879
        vector<ComboAddress> candidates;
×
880

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

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

896
        boost::format fmt2("%02x%02x%02x%02x");
×
897
        for(int i=3; i>=0; --i)
×
898
          fmt2 % atoi(labels[i].c_str());
×
899

900
        fmt % (fmt2.str());
×
901

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

926
        // allow a word without - in front, as long as it does not contain anything that could be a number
927
        size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
×
928
        if (nonhexprefix > 0) {
×
929
          input = input.substr(nonhexprefix);
×
930
        }
×
931

932
        // either hex string, or 12-13-14-15
933
        vector<string> ip_parts;
×
934

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

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

997
            ComboAddress ca(fulladdress);
×
998
            return ca.toString();
×
999
          }
×
1000
        }
×
1001
      }
×
1002

1003
      return std::string("::");
×
1004
    });
×
1005
  lua.writeFunction("createReverse6", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
1006
      vector<ComboAddress> candidates;
×
1007

1008
      try {
×
1009
        auto labels= s_lua_record_ctx->qname.getRawLabels();
×
1010
        if(labels.size()<32)
×
1011
          return std::string("unknown");
×
1012
        boost::format fmt(format);
×
1013
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
1014

1015

1016
        string together;
×
1017
        vector<string> quads;
×
1018
        for(int i=0; i<8; ++i) {
×
1019
          if(i)
×
1020
            together+=":";
×
1021
          string lquad;
×
1022
          for(int j=0; j <4; ++j) {
×
1023
            lquad.append(1, labels[31-i*4-j][0]);
×
1024
            together += labels[31-i*4-j][0];
×
1025
          }
×
1026
          quads.push_back(lquad);
×
1027
        }
×
1028
        ComboAddress ip6(together,0);
×
1029

1030
        if(e) {
×
1031
          auto& addrs=*e;
×
1032
          for(const auto& addr: addrs) {
×
1033
            // this makes sure we catch all forms of the address
1034
            if(ComboAddress(addr.first,0)==ip6)
×
1035
              return addr.second;
×
1036
          }
×
1037
        }
×
1038

1039
        string dashed=ip6.toString();
×
1040
        boost::replace_all(dashed, ":", "-");
×
1041

1042
        for(int i=31; i>=0; --i)
×
1043
          fmt % labels[i];
×
1044
        fmt % dashed;
×
1045

1046
        for(const auto& lquad : quads)
×
1047
          fmt % lquad;
×
1048

1049
        return fmt.str();
×
1050
      }
×
1051
      catch(std::exception& ex) {
×
1052
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.what()<<endl;
×
1053
      }
×
1054
      catch(PDNSException& ex) {
×
1055
        g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl;
×
1056
      }
×
1057
      return std::string("unknown");
×
1058
    });
×
1059

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

1063
      if (nmg.match(ComboAddress(address))) {
×
NEW
1064
        return {address};
×
1065
      } else {
×
1066
        if (fallback) {
×
NEW
1067
          if (fallback->empty()) {
×
1068
            // if fallback is an empty string, return an empty array
NEW
1069
            return {};
×
NEW
1070
          }
×
NEW
1071
          return {*fallback};
×
UNCOV
1072
        }
×
1073

1074
        if (ca.isIPv4()) {
×
NEW
1075
          return {string("0.0.0.0")};
×
1076
        } else {
×
NEW
1077
          return {string("::")};
×
1078
        }
×
1079
      }
×
1080
    });
×
1081

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

1098
      auto checker = [](const ComboAddress& addr, const opts_t& opts) {
×
1099
        return g_up.isUp(addr, opts);
×
1100
      };
×
1101
      return genericIfUp(ips, options, checker, port);
×
1102
    });
×
1103

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

1110
      ComboAddress ca_unspec;
×
1111
      ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1112

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

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

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

1137
  lua.writeFunction("ifurlup", [](const std::string& url,
×
1138
                                          const boost::variant<iplist_t, ipunitlist_t>& ips,
×
1139
                                          boost::optional<opts_t> options) {
×
1140

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

1155
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
×
1156
      vector<string> items = convStringList(ips);
×
1157
          return pickRandomSample<string>(n, items);
×
1158
    });
×
1159

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

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

1182
      items.reserve(ips.size());
×
1183
      for (auto& entry : ips) {
×
1184
        items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
×
1185
      }
×
1186

1187
      return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1188
    });
×
1189

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

1198
      items.reserve(ips.size());
×
1199
      for(auto& i : ips)
×
1200
      {
×
1201
        items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
×
1202
      }
×
1203

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

1214
    items.reserve(ips.size());
×
1215
    for (const auto& entry : ips) {
×
1216
      items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
×
1217
    }
×
1218

1219
    return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1220
  });
×
1221

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

1225
      return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1226

1227
    });
×
1228

1229
  if (g_luaRecordExecLimit > 0) {
×
1230
      lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1231
  }
×
1232

1233
  lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){
×
1234
      throw std::runtime_error("Script took too long");
×
1235
    });
×
1236

1237
  lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
×
1238
    return getGeo(ip, attr);
×
1239
  });
×
1240

1241
  typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
×
1242

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

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

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

1328
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
×
1329
      vector<string> result;
×
1330
          result.reserve(ips.size());
×
1331

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

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

1354
      if (!getAuth(rec, qtype, &soaData)) {
×
1355
        return ret;
×
1356
      }
×
1357

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

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

1390
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)
1391
{
×
1392
  if(!LUA ||                  // we don't have a Lua state yet
×
1393
     !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1394
    LUA = make_unique<AuthLua4>();
×
1395
    setupLuaRecords(*LUA->getLua());
×
1396
  }
×
1397

1398
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1399

1400
  LuaContext& lua = *LUA->getLua();
×
1401

1402
  s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1403
  s_lua_record_ctx->qname = query;
×
1404
  s_lua_record_ctx->zone = zone;
×
1405
  s_lua_record_ctx->zoneid = zoneid;
×
1406

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

1426
  try {
×
1427
    string actual;
×
1428
    if(!code.empty() && code[0]!=';')
×
1429
      actual = "return " + code;
×
1430
    else
×
1431
      actual = code.substr(1);
×
1432

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

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

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

1462
  return ret;
×
1463
}
×
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