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

PowerDNS / pdns / 13547652042

26 Feb 2025 03:37PM UTC coverage: 64.518% (+0.03%) from 64.49%
13547652042

Pull #15222

github

web-flow
Merge 97800540e into 47224bc4c
Pull Request #15222: Damage control in Lua createForward()

38345 of 90428 branches covered (42.4%)

Branch coverage included in aggregate %.

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

48 existing lines in 13 files now uncovered.

127684 of 166910 relevant lines covered (76.5%)

4547589.12 hits per line

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

0.19
/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
   add attribute for certificate check in genericIfUp
33

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
    /* current weight */
75
    std::atomic<int> weight{0};
76
    /* first check? */
77
    std::atomic<bool> first{true};
78
    /* number of successive checks returning failure */
79
    std::atomic<unsigned int> failures{0};
80
    /* last time the status was accessed */
81
    std::atomic<time_t> lastAccess{0};
82
    /* last time the status was modified */
83
    std::atomic<time_t> lastStatusUpdate{0};
84
  };
85

86
public:
87
  IsUpOracle()
88
  {
256✔
89
    d_checkerThreadStarted.clear();
256✔
90
  }
256✔
91
  ~IsUpOracle() = default;
×
92
  int isUp(const ComboAddress& remote, const opts_t& opts);
93
  int isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts);
94
  //NOLINTNEXTLINE(readability-identifier-length)
95
  int isUp(const CheckDesc& cd);
96

97
private:
98
  void checkURL(const CheckDesc& cd, const bool status, const bool first) // NOLINT(readability-identifier-length)
99
  {
×
100
    setThreadName("pdns/lua-c-url");
×
101

102
    string remstring;
×
103
    try {
×
104
      int timeout = 2;
×
105
      if (cd.opts.count("timeout")) {
×
106
        timeout = std::atoi(cd.opts.at("timeout").c_str());
×
107
      }
×
108
      string useragent = productName();
×
109
      if (cd.opts.count("useragent")) {
×
110
        useragent = cd.opts.at("useragent");
×
111
      }
×
112
      size_t byteslimit = 0;
×
113
      if (cd.opts.count("byteslimit")) {
×
114
        byteslimit = static_cast<size_t>(std::atoi(cd.opts.at("byteslimit").c_str()));
×
115
      }
×
116
      MiniCurl mc(useragent);
×
117

118
      string content;
×
119
      const ComboAddress* rem = nullptr;
×
120
      if(cd.rem.sin4.sin_family != AF_UNSPEC) {
×
121
        rem = &cd.rem;
×
122
        remstring = rem->toString();
×
123
      } else {
×
124
        remstring = "[externally checked IP]";
×
125
      }
×
126

127
      if (cd.opts.count("source")) {
×
128
        ComboAddress src(cd.opts.at("source"));
×
129
        content=mc.getURL(cd.url, rem, &src, timeout, false, false, byteslimit);
×
130
      }
×
131
      else {
×
132
        content=mc.getURL(cd.url, rem, nullptr, timeout, false, false, byteslimit);
×
133
      }
×
134
      if (cd.opts.count("stringmatch") && content.find(cd.opts.at("stringmatch")) == string::npos) {
×
135
        throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % cd.opts.at("stringmatch")));
×
136
      }
×
137

138
      int weight = 0;
×
139
      try {
×
140
        weight = stoi(content);
×
141
        if(!status) {
×
142
          g_log<<Logger::Info<<"Lua record monitoring declaring "<<remstring<<" UP for URL "<<cd.url<<"!"<<" with WEIGHT "<<content<<"!"<<endl;
×
143
        }
×
144
      }
×
145
      catch (const std::exception&) {
×
146
        if(!status) {
×
147
          g_log<<Logger::Info<<"Lua record monitoring declaring "<<remstring<<" UP for URL "<<cd.url<<"!"<<endl;
×
148
        }
×
149
      }
×
150

151
      setWeight(cd, weight);
×
152
      setUp(cd);
×
153
    }
×
154
    catch(std::exception& ne) {
×
155
      if(status || first)
×
156
        g_log<<Logger::Info<<"Lua record monitoring declaring "<<remstring<<" DOWN for URL "<<cd.url<<", error: "<<ne.what()<<endl;
×
157
      setWeight(cd, 0);
×
158
      setDown(cd);
×
159
    }
×
160
  }
×
161
  void checkTCP(const CheckDesc& cd, const bool status, const bool first) { // NOLINT(readability-identifier-length)
×
162
    setThreadName("pdns/lua-c-tcp");
×
163
    try {
×
164
      int timeout = 2;
×
165
      if (cd.opts.count("timeout")) {
×
166
        timeout = std::atoi(cd.opts.at("timeout").c_str());
×
167
      }
×
168
      Socket s(cd.rem.sin4.sin_family, SOCK_STREAM);
×
169
      ComboAddress src;
×
170
      s.setNonBlocking();
×
171
      if (cd.opts.count("source")) {
×
172
        src = ComboAddress(cd.opts.at("source"));
×
173
        s.bind(src);
×
174
      }
×
175
      s.connect(cd.rem, timeout);
×
176
      if (!status) {
×
177
        g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" ";
×
178
        if(cd.opts.count("source"))
×
179
          g_log<<"(source "<<src.toString()<<") ";
×
180
        g_log<<"UP!"<<endl;
×
181
      }
×
182
      setUp(cd);
×
183
    }
×
184
    catch (const NetworkError& ne) {
×
185
      if(status || first) {
×
186
        g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" DOWN: "<<ne.what()<<endl;
×
187
      }
×
188
      setDown(cd);
×
189
    }
×
190
  }
×
191
  void checkThread()
192
  {
×
193
    setThreadName("pdns/luaupcheck");
×
194
    while (true)
×
195
    {
×
196
      std::chrono::system_clock::time_point checkStart = std::chrono::system_clock::now();
×
197
      std::vector<std::future<void>> results;
×
198
      std::vector<CheckDesc> toDelete;
×
199
      time_t interval{g_luaHealthChecksInterval};
×
200
      {
×
201
        // make sure there's no insertion
202
        auto statuses = d_statuses.read_lock();
×
203
        for (auto& it: *statuses) {
×
204
          auto& desc = it.first;
×
205
          auto& state = it.second;
×
206
          time_t checkInterval{0};
×
207
          auto lastAccess = std::chrono::system_clock::from_time_t(state->lastAccess);
×
208

209
          if (desc.opts.count("interval") != 0) {
×
210
            checkInterval = std::atoi(desc.opts.at("interval").c_str());
×
211
            if (checkInterval != 0) {
×
212
              interval = std::gcd(interval, checkInterval);
×
213
            }
×
214
          }
×
215

216
          if (not state->first) {
×
217
            time_t nextCheckSecond = state->lastStatusUpdate;
×
218
            if (checkInterval != 0) {
×
219
               nextCheckSecond += checkInterval;
×
220
            }
×
221
            else {
×
222
               nextCheckSecond += g_luaHealthChecksInterval;
×
223
            }
×
224
            if (checkStart < std::chrono::system_clock::from_time_t(nextCheckSecond)) {
×
225
              continue; // too early
×
226
            }
×
227
          }
×
228

229
          if (desc.url.empty()) { // TCP
×
230
            results.push_back(std::async(std::launch::async, &IsUpOracle::checkTCP, this, desc, state->status.load(), state->first.load()));
×
231
          } else { // URL
×
232
            results.push_back(std::async(std::launch::async, &IsUpOracle::checkURL, this, desc, state->status.load(), state->first.load()));
×
233
          }
×
234
          // Give it a chance to run at least once.
235
          // If minimumFailures * interval > lua-health-checks-expire-delay, then a down status will never get reported.
236
          // This is unlikely to be a problem in practice due to the default value of the expire delay being one hour.
237
          if (not state->first &&
×
238
              lastAccess < (checkStart - std::chrono::seconds(g_luaHealthChecksExpireDelay))) {
×
239
            toDelete.push_back(desc);
×
240
          }
×
241
        }
×
242
      }
×
243
      // we can release the lock as nothing will be deleted
244
      for (auto& future: results) {
×
245
        future.wait();
×
246
      }
×
247
      if (!toDelete.empty()) {
×
248
        auto statuses = d_statuses.write_lock();
×
249
        for (auto& it: toDelete) {
×
250
          statuses->erase(it);
×
251
        }
×
252
      }
×
253

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

257
      std::this_thread::sleep_until(checkStart + std::chrono::seconds(interval));
×
258
    }
×
259
  }
×
260

261
  typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
262
  SharedLockGuarded<statuses_t> d_statuses;
263

264
  std::unique_ptr<std::thread> d_checkerThread;
265
  std::atomic_flag d_checkerThreadStarted;
266

267
  void setStatus(const CheckDesc& cd, bool status)
268
  {
×
269
    auto statuses = d_statuses.write_lock();
×
270
    auto& state = (*statuses)[cd];
×
271
    state->lastStatusUpdate = time(nullptr);
×
272
    state->first = false;
×
273
    if (status) {
×
274
      state->failures = 0;
×
275
      state->status = true;
×
276
    } else {
×
277
      unsigned int minimumFailures = 1;
×
278
      if (cd.opts.count("minimumFailures") != 0) {
×
279
        unsigned int value = std::atoi(cd.opts.at("minimumFailures").c_str());
×
280
        if (value != 0) {
×
281
          minimumFailures = std::max(minimumFailures, value);
×
282
        }
×
283
      }
×
284
      // Since `status' was set to false at constructor time, we need to
285
      // recompute its value unconditionally to expose "down, but not enough
286
      // times yet" targets as up.
287
      state->status = ++state->failures < minimumFailures;
×
288
    }
×
289
  }
×
290

291
  //NOLINTNEXTLINE(readability-identifier-length)
292
  void setWeight(const CheckDesc& cd, int weight){
×
293
    auto statuses = d_statuses.write_lock();
×
294
    auto& state = (*statuses)[cd];
×
295
    state->weight = weight;
×
296
  }
×
297

298
  void setDown(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
299
  {
×
300
    //NOLINTNEXTLINE(readability-identifier-length)
×
301
    CheckDesc cd{rem, url, opts};
×
302
    setStatus(cd, false);
×
303
  }
×
304

305
  void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
306
  {
×
307
    CheckDesc cd{rem, url, opts};
×
308

×
309
    setStatus(cd, true);
×
310
  }
×
311

312
  void setDown(const CheckDesc& cd)
313
  {
×
314
    setStatus(cd, false);
×
315
  }
×
316

317
  void setUp(const CheckDesc& cd)
318
  {
×
319
    setStatus(cd, true);
×
320
  }
×
321
};
322

323
//NOLINTNEXTLINE(readability-identifier-length)
324
int IsUpOracle::isUp(const CheckDesc& cd)
325
{
×
326
  if (!d_checkerThreadStarted.test_and_set()) {
×
327
    d_checkerThread = std::make_unique<std::thread>([this] { return checkThread(); });
×
328
  }
×
329
  time_t now = time(nullptr);
×
330
  {
×
331
    auto statuses = d_statuses.read_lock();
×
332
    auto iter = statuses->find(cd);
×
333
    if (iter != statuses->end()) {
×
334
      iter->second->lastAccess = now;
×
335
      if (iter->second->weight > 0) {
×
336
        return iter->second->weight;
×
337
      }
×
338
      return static_cast<int>(iter->second->status);
×
339
    }
×
340
  }
×
341
  // try to parse options so we don't insert any malformed content
342
  if (cd.opts.count("source")) {
×
343
    ComboAddress src(cd.opts.at("source"));
×
344
  }
×
345
  {
×
346
    auto statuses = d_statuses.write_lock();
×
347
    // Make sure we don't insert new entry twice now we have the lock
348
    if (statuses->find(cd) == statuses->end()) {
×
349
      (*statuses)[cd] = std::make_unique<CheckState>(now);
×
350
    }
×
351
  }
×
352
  return 0;
×
353
}
×
354

355
int IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts)
356
{
×
357
  CheckDesc cd{remote, "", opts};
×
358
  return isUp(cd);
×
359
}
×
360

361
int IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts)
362
{
×
363
  CheckDesc cd{remote, url, opts};
×
364
  return isUp(cd);
×
365
}
×
366

367
IsUpOracle g_up;
368
namespace {
369
template<typename T, typename C>
370
bool doCompare(const T& var, const std::string& res, const C& cmp)
371
{
×
372
  if(auto country = boost::get<string>(&var))
×
373
    return cmp(*country, res);
×
374

375
  auto countries=boost::get<vector<pair<int,string> > >(&var);
×
376
  for(const auto& country : *countries) {
×
377
    if(cmp(country.second, res))
×
378
      return true;
×
379
  }
×
380
  return false;
×
381
}
×
382
}
383

384
static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
385
{
×
386
  static bool initialized;
×
387
  extern std::function<std::string(const std::string& ip, int)> g_getGeo;
×
388
  if(!g_getGeo) {
×
389
    if(!initialized) {
×
390
      g_log<<Logger::Error<<"Lua record attempted to use GeoIPBackend functionality, but backend not launched"<<endl;
×
391
      initialized=true;
×
392
    }
×
393
    return "unknown";
×
394
  }
×
395
  else
×
396
    return g_getGeo(ip, (int)qa);
×
397
}
×
398

399
template <typename T>
400
static T pickRandom(const vector<T>& items)
401
{
×
402
  if (items.empty()) {
×
403
    throw std::invalid_argument("The items list cannot be empty");
×
404
  }
×
405
  return items[dns_random(items.size())];
×
406
}
×
407

408
template <typename T>
409
static T pickHashed(const ComboAddress& who, const vector<T>& items)
410
{
×
411
  if (items.empty()) {
×
412
    throw std::invalid_argument("The items list cannot be empty");
×
413
  }
×
414
  ComboAddress::addressOnlyHash aoh;
×
415
  return items[aoh(who) % items.size()];
×
416
}
×
417

418
template <typename T>
419
static T pickWeightedRandom(const vector< pair<int, T> >& items)
420
{
×
421
  if (items.empty()) {
×
422
    throw std::invalid_argument("The items list cannot be empty");
×
423
  }
×
424
  int sum=0;
×
425
  vector< pair<int, T> > pick;
×
426
  pick.reserve(items.size());
×
427

428
  for(auto& i : items) {
×
429
    sum += i.first;
×
430
    pick.emplace_back(sum, i.second);
×
431
  }
×
432

433
  if (sum == 0) {
×
434
    throw std::invalid_argument("The sum of items cannot be zero");
×
435
  }
×
436

437
  int r = dns_random(sum);
×
438
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
439
  return p->second;
×
440
}
×
441

442
template <typename T>
443
static T pickWeightedHashed(const ComboAddress& bestwho, const vector< pair<int, T> >& items)
444
{
×
445
  if (items.empty()) {
×
446
    throw std::invalid_argument("The items list cannot be empty");
×
447
  }
×
448
  int sum=0;
×
449
  vector< pair<int, T> > pick;
×
450
  pick.reserve(items.size());
×
451

452
  for(auto& i : items) {
×
453
    sum += i.first;
×
454
    pick.push_back({sum, i.second});
×
455
  }
×
456

457
  if (sum == 0) {
×
458
    throw std::invalid_argument("The sum of items cannot be zero");
×
459
  }
×
460

461
  ComboAddress::addressOnlyHash aoh;
×
462
  int r = aoh(bestwho) % sum;
×
463
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
464
  return p->second;
×
465
}
×
466

467
template <typename T>
468
static T pickWeightedNameHashed(const DNSName& dnsname, vector< pair<int, T> >& items)
469
{
×
470
  if (items.empty()) {
×
471
    throw std::invalid_argument("The items list cannot be empty");
×
472
  }
×
473
  size_t sum=0;
×
474
  vector< pair<int, T> > pick;
×
475
  pick.reserve(items.size());
×
476

477
  for(auto& i : items) {
×
478
    sum += i.first;
×
479
    pick.push_back({sum, i.second});
×
480
  }
×
481

482
  if (sum == 0) {
×
483
    throw std::invalid_argument("The sum of items cannot be zero");
×
484
  }
×
485

486
  size_t r = dnsname.hash() % sum;
×
487
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
488
  return p->second;
×
489
}
×
490

491
template <typename T>
492
static vector<T> pickRandomSample(int n, const vector<T>& items)
493
{
×
494
  if (items.empty()) {
×
495
    throw std::invalid_argument("The items list cannot be empty");
×
496
  }
×
497

498
  vector<T> pick;
×
499
  pick.reserve(items.size());
×
500

501
  for(auto& item : items) {
×
502
    pick.push_back(item);
×
503
  }
×
504

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

507
  if (count == 0) {
×
508
    return vector<T>();
×
509
  }
×
510

511
  std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
×
512

513
  vector<T> result = {pick.begin(), pick.begin() + count};
×
514
  return result;
×
515
}
×
516

517
static bool getLatLon(const std::string& ip, double& lat, double& lon)
518
{
×
519
  string inp = getGeo(ip, GeoIPInterface::Location);
×
520
  if(inp.empty())
×
521
    return false;
×
522
  lat=atof(inp.c_str());
×
523
  auto pos=inp.find(' ');
×
524
  if(pos != string::npos)
×
525
    lon=atof(inp.c_str() + pos);
×
526
  return true;
×
527
}
×
528

529
static bool getLatLon(const std::string& ip, string& loc)
530
{
×
531
  int latdeg, latmin, londeg, lonmin;
×
532
  double latsec, lonsec;
×
533
  char lathem='X', lonhem='X';
×
534

535
  double lat = 0, lon = 0;
×
536
  if(!getLatLon(ip, lat, lon))
×
537
    return false;
×
538

539
  if(lat > 0) {
×
540
    lathem='N';
×
541
  }
×
542
  else {
×
543
    lat = -lat;
×
544
    lathem='S';
×
545
  }
×
546

547
  if(lon > 0) {
×
548
    lonhem='E';
×
549
  }
×
550
  else {
×
551
    lon = -lon;
×
552
    lonhem='W';
×
553
  }
×
554

555
  latdeg = lat;
×
556
  latmin = (lat - latdeg)*60.0;
×
557
  latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
×
558

559
  londeg = lon;
×
560
  lonmin = (lon - londeg)*60.0;
×
561
  lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
×
562

563
  // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
564

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

567
  loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
×
568
  return true;
×
569
}
×
570

571
static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips)
572
{
×
573
  if (wips.empty()) {
×
574
    throw std::invalid_argument("The IP list cannot be empty");
×
575
  }
×
576
  map<double, vector<ComboAddress> > ranked;
×
577
  double wlat=0, wlon=0;
×
578
  getLatLon(bestwho.toString(), wlat, wlon);
×
579
  //        cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
580
  vector<string> ret;
×
581
  for(const auto& c : wips) {
×
582
    double lat=0, lon=0;
×
583
    getLatLon(c.toString(), lat, lon);
×
584
    //          cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
585
    double latdiff = wlat-lat;
×
586
    double londiff = wlon-lon;
×
587
    if(londiff > 180)
×
588
      londiff = 360 - londiff;
×
589
    double dist2=latdiff*latdiff + londiff*londiff;
×
590
    //          cout<<"    distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
591
    ranked[dist2].push_back(c);
×
592
  }
×
593
  return ranked.begin()->second[dns_random(ranked.begin()->second.size())];
×
594
}
×
595

596
static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid)
597
{
×
598
  static LockGuarded<UeberBackend> s_ub;
×
599

600
  DNSZoneRecord dr;
×
601
  vector<DNSZoneRecord> ret;
×
602
  {
×
603
    auto ub = s_ub.lock();
×
604
    ub->lookup(QType(qtype), name, zoneid);
×
605
    while (ub->get(dr)) {
×
606
      ret.push_back(dr);
×
607
    }
×
608
  }
×
609
  return ret;
×
610
}
×
611

612
static bool getAuth(const DNSName& name, uint16_t qtype, SOAData* soaData)
613
{
×
614
  static LockGuarded<UeberBackend> s_ub;
×
615

616
  {
×
617
    auto ueback = s_ub.lock();
×
618
    return ueback->getAuth(name, qtype, soaData);
×
619
  }
×
620
}
×
621

622
static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
623
{
×
624
  string selector=defaultValue;
×
625
  if(options) {
×
626
    if(options->count(name))
×
627
      selector=options->find(name)->second;
×
628
  }
×
629
  return selector;
×
630
}
×
631

632
static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
633
{
×
634
  vector<ComboAddress> ret;
×
635

636
  if(selector=="all")
×
637
    return candidates;
×
638
  else if(selector=="random")
×
639
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
640
  else if(selector=="pickclosest")
×
641
    ret.emplace_back(pickclosest(bestwho, candidates));
×
642
  else if(selector=="hashed")
×
643
    ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates));
×
644
  else {
×
645
    g_log<<Logger::Warning<<"Lua record called with unknown selector '"<<selector<<"'"<<endl;
×
646
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
647
  }
×
648

649
  return ret;
×
650
}
×
651

652
static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
653
{
×
654
  vector<string> result;
×
655
  result.reserve(items.size());
×
656

657
  for (const auto& item : items) {
×
658
    result.emplace_back(item.toString());
×
659
  }
×
660

661
  return result;
×
662
}
×
663

664
static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
665
{
×
666
  vector<ComboAddress> result;
×
667
  result.reserve(items.size());
×
668

669
  for(const auto& item : items) {
×
670
    result.emplace_back(ComboAddress(item.second, port));
×
671
  }
×
672

673
  return result;
×
674
}
×
675

676
/**
677
 * Reads and unify single or multiple sets of ips :
678
 * - {'192.0.2.1', '192.0.2.2'}
679
 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
680
 */
681

682
static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
683
{
×
684
  vector<vector<ComboAddress>> candidates;
×
685

686
  if(auto simple = boost::get<iplist_t>(&items)) {
×
687
    vector<ComboAddress> unit = convComboAddressList(*simple, port);
×
688
    candidates.push_back(unit);
×
689
  } else {
×
690
    auto units = boost::get<ipunitlist_t>(items);
×
691
    for(const auto& u : units) {
×
692
      vector<ComboAddress> unit = convComboAddressList(u.second, port);
×
693
      candidates.push_back(unit);
×
694
    }
×
695
  }
×
696
  return candidates;
×
697
}
×
698

699
static vector<string> convStringList(const iplist_t& items)
700
{
×
701
  vector<string> result;
×
702
  result.reserve(items.size());
×
703

704
  for(const auto& item : items) {
×
705
    result.emplace_back(item.second);
×
706
  }
×
707

708
  return result;
×
709
}
×
710

711
static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
712
{
×
713
  vector<pair<int,string> > result;
×
714
  result.reserve(items.size());
×
715

716
  for(const auto& item : items) {
×
717
    result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
×
718
  }
×
719

720
  return result;
×
721
}
×
722

723
bool g_LuaRecordSharedState;
724

725
typedef struct AuthLuaRecordContext
726
{
727
  ComboAddress          bestwho;
728
  DNSName               qname;
729
  DNSZoneRecord         zone_record;
730
  DNSName               zone;
731
} lua_record_ctx_t;
732

733
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
734

735
/*
736
 *  Holds computed hashes for a given entry
737
 */
738
struct EntryHashesHolder
739
{
740
  std::atomic<size_t> weight;
741
  std::string entry;
742
  SharedLockGuarded<std::vector<unsigned int>> hashes;
743
  std::atomic<time_t> lastUsed;
744

745
  EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
×
746
  }
×
747

748
  bool hashesComputed() {
×
749
    return weight == hashes.read_lock()->size();
×
750
  }
×
751
  void hash() {
×
752
    auto locked = hashes.write_lock();
×
753
    locked->clear();
×
754
    locked->reserve(weight);
×
755
    size_t count = 0;
×
756
    while (count < weight) {
×
757
      auto value = boost::str(boost::format("%s-%d") % entry % count);
×
758
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
759
      auto whash = burtle(reinterpret_cast<const unsigned char*>(value.data()), value.size(), 0);
×
760
      locked->push_back(whash);
×
761
      ++count;
×
762
    }
×
763
    std::sort(locked->begin(), locked->end());
×
764
  }
×
765
};
766

767
using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
768

769
static SharedLockGuarded<std::map<
770
  zone_hashes_key_t, // zoneid qname entry
771
  std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
772
  >>
773
s_zone_hashes;
774

775
static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
776

777
/**
778
 * every ~g_luaConsistentHashesCleanupInterval, do a cleanup to delete entries that haven't been used in the last g_luaConsistentHashesExpireDelay
779
 */
780
static void cleanZoneHashes()
781
{
×
782
  auto now = time(nullptr);
×
783
  if (s_lastConsistentHashesCleanup > (now - g_luaConsistentHashesCleanupInterval)) {
×
784
    return ;
×
785
  }
×
786
  s_lastConsistentHashesCleanup = now;
×
787
  std::vector<zone_hashes_key_t> toDelete{};
×
788
  {
×
789
    auto locked = s_zone_hashes.read_lock();
×
790
    auto someTimeAgo = now - g_luaConsistentHashesExpireDelay;
×
791

792
    for (const auto& [key, entry]: *locked) {
×
793
      if (entry->lastUsed > someTimeAgo) {
×
794
        toDelete.push_back(key);
×
795
      }
×
796
    }
×
797
  }
×
798
  if (!toDelete.empty()) {
×
799
    auto wlocked = s_zone_hashes.write_lock();
×
800
    for (const auto& key : toDelete) {
×
801
      wlocked->erase(key);
×
802
    }
×
803
  }
×
804
}
×
805

806
static std::vector<std::shared_ptr<EntryHashesHolder>> getCHashedEntries(const int zoneId, const std::string& queryName, const std::vector<std::pair<int, std::string>>& items)
807
{
×
808
  std::vector<std::shared_ptr<EntryHashesHolder>> result{};
×
809
  std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
×
810

811
  {
×
812
    time_t now = time(nullptr);
×
813
    auto locked = s_zone_hashes.read_lock();
×
814

815
    for (const auto& [weight, entry]: items) {
×
816
      auto key = std::make_tuple(zoneId, queryName, entry);
×
817
      if (locked->count(key) == 0) {
×
818
        newEntries[key] = std::make_shared<EntryHashesHolder>(weight, entry, now);
×
819
      } else {
×
820
        locked->at(key)->weight = weight;
×
821
        locked->at(key)->lastUsed = now;
×
822
        result.push_back(locked->at(key));
×
823
      }
×
824
    }
×
825
  }
×
826
  if (!newEntries.empty()) {
×
827
    auto wlocked = s_zone_hashes.write_lock();
×
828

829
    for (auto& [key, entry]: newEntries) {
×
830
      result.push_back(entry);
×
831
      (*wlocked)[key] = std::move(entry);
×
832
    }
×
833
  }
×
834

835
  return result;
×
836
}
×
837

838
static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
839
{
×
840
  const auto& zoneId = s_lua_record_ctx->zone_record.domain_id;
×
841
  const auto queryName = s_lua_record_ctx->qname.toString();
×
842
  unsigned int sel = std::numeric_limits<unsigned int>::max();
×
843
  unsigned int min = std::numeric_limits<unsigned int>::max();
×
844

845
  boost::optional<std::string> ret;
×
846
  boost::optional<std::string> first;
×
847

848
  cleanZoneHashes();
×
849

850
  auto entries = getCHashedEntries(zoneId, queryName, items);
×
851

852
  ComboAddress::addressOnlyHash addrOnlyHash;
×
853
  auto qhash = addrOnlyHash(bestwho);
×
854
  for (const auto& entry : entries) {
×
855
    if (!entry->hashesComputed()) {
×
856
      entry->hash();
×
857
    }
×
858
    {
×
859
      const auto hashes = entry->hashes.read_lock();
×
860
      if (!hashes->empty()) {
×
861
        if (min > *(hashes->begin())) {
×
862
          min = *(hashes->begin());
×
863
          first = entry->entry;
×
864
        }
×
865

866
        auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
×
867
        if (hash_it != hashes->end()) {
×
868
          if (*hash_it < sel) {
×
869
            sel = *hash_it;
×
870
            ret = entry->entry;
×
871
          }
×
872
        }
×
873
      }
×
874
    }
×
875
  }
×
876
  if (ret != boost::none) {
×
877
    return *ret;
×
878
  }
×
879
  if (first != boost::none) {
×
880
    return *first;
×
881
  }
×
882
  return {};
×
883
}
×
884

885
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)
886
{
×
887
  vector<vector<ComboAddress> > candidates;
×
888
  opts_t opts;
×
889
  if(options)
×
890
    opts = *options;
×
891

892
  candidates = convMultiComboAddressList(ips, port);
×
893

894
  for(const auto& unit : candidates) {
×
895
    vector<ComboAddress> available;
×
896
    for(const auto& c : unit) {
×
897
      if (upcheckf(c, opts)) {
×
898
        available.push_back(c);
×
899
      }
×
900
    }
×
901
    if(!available.empty()) {
×
902
      vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
903
      return convComboAddressListToString(res);
×
904
    }
×
905
  }
×
906

907
  // All units down, apply backupSelector on all candidates
908
  vector<ComboAddress> ret{};
×
909
  for(const auto& unit : candidates) {
×
910
    ret.insert(ret.end(), unit.begin(), unit.end());
×
911
  }
×
912

913
  vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
×
914
  return convComboAddressListToString(res);
×
915
}
×
916

917
static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cognitive-complexity)
918
{
×
919
  lua.writeFunction("latlon", []() {
×
920
      double lat = 0, lon = 0;
×
921
      getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
×
922
      return std::to_string(lat)+" "+std::to_string(lon);
×
923
    });
×
924
  lua.writeFunction("latlonloc", []() {
×
925
      string loc;
×
926
      getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
×
927
      return loc;
×
928
  });
×
929
  lua.writeFunction("closestMagic", []() {
×
930
      vector<ComboAddress> candidates;
×
931
      // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
932
      for(auto l : s_lua_record_ctx->qname.getRawLabels()) {
×
933
        std::replace(l.begin(), l.end(), '-', '.');
×
934
        try {
×
935
          candidates.emplace_back(l);
×
936
        } catch (const PDNSException& e) {
×
937
          // no need to continue as we most likely reached the end of the ip list
938
          break ;
×
939
        }
×
940
      }
×
941
      return pickclosest(s_lua_record_ctx->bestwho, candidates).toString();
×
942
    });
×
943
  lua.writeFunction("latlonMagic", [](){
×
944
      auto labels= s_lua_record_ctx->qname.getRawLabels();
×
945
      if(labels.size()<4)
×
946
        return std::string("unknown");
×
947
      double lat = 0, lon = 0;
×
948
      getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
×
949
      return std::to_string(lat)+" "+std::to_string(lon);
×
950
    });
×
951

952

953
  lua.writeFunction("createReverse", [](string format, boost::optional<std::unordered_map<string,string>> e){
×
954
      try {
×
955
        auto labels = s_lua_record_ctx->qname.getRawLabels();
×
956
        if(labels.size()<4)
×
957
          return std::string("unknown");
×
958

959
        vector<ComboAddress> candidates;
×
960

961
        // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
962
        // e["1.2.3.4"]="bert.powerdns.com" then provides an exception
963
        if(e) {
×
964
          ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
×
965
          const auto& uom = *e;
×
966
          for(const auto& c : uom)
×
967
            if(ComboAddress(c.first, 0) == req)
×
968
              return c.second;
×
969
        }
×
970
        boost::format fmt(format);
×
971
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
972
        fmt % labels[3] % labels[2] % labels[1] % labels[0];
×
973

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

976
        boost::format fmt2("%02x%02x%02x%02x");
×
977
        for(int i=3; i>=0; --i)
×
978
          fmt2 % atoi(labels[i].c_str());
×
979

980
        fmt % (fmt2.str());
×
981

982
        return fmt.str();
×
983
      }
×
984
      catch(std::exception& ex) {
×
985
        g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
×
986
      }
×
987
      return std::string("error");
×
988
    });
×
989
  lua.writeFunction("createForward", []() {
×
990
      static string allZerosIP{"0.0.0.0"};
×
NEW
991
      try {
×
NEW
992
        DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
NEW
993
        if (!record_name.isWildcard()) {
×
994
          return allZerosIP;
×
995
        }
×
NEW
996
        record_name.chopOff();
×
NEW
997
        DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
998

999
        // parts is something like ["1", "2", "3", "4", "static"] or
1000
        // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
NEW
1001
        auto parts = rel.getRawLabels();
×
1002
        // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
NEW
1003
        if (parts.size() >= 4) {
×
NEW
1004
          ComboAddress address(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
×
NEW
1005
          return address.toString();
×
UNCOV
1006
        }
×
NEW
1007
        if (!parts.empty()) {
×
NEW
1008
          auto& input = parts.at(0);
×
1009

1010
          // allow a word without - in front, as long as it does not contain anything that could be a number
NEW
1011
          size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
×
NEW
1012
          if (nonhexprefix > 0) {
×
NEW
1013
            input = input.substr(nonhexprefix);
×
NEW
1014
          }
×
1015

1016
          // either hex string, or 12-13-14-15
NEW
1017
          vector<string> ip_parts;
×
1018

NEW
1019
          stringtok(ip_parts, input, "-");
×
NEW
1020
          if (ip_parts.size() >= 4) {
×
1021
            // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
NEW
1022
            string ret;
×
NEW
1023
            for (size_t index=4; index > 0; index--) {
×
NEW
1024
              auto octet = ip_parts.at(ip_parts.size() - index);
×
NEW
1025
              auto octetVal = std::stol(octet); // may throw
×
1026
              if (octetVal >= 0 && octetVal <= 255) {
×
NEW
1027
                ret += octet + ".";
×
1028
              } else {
×
1029
                return allZerosIP;
×
1030
              }
×
1031
            }
×
NEW
1032
            ret.resize(ret.size() - 1); // remove trailing dot after last octet
×
NEW
1033
            return ret;
×
1034
          }
×
NEW
1035
          if (input.length() >= 8) {
×
NEW
1036
            auto last8 = input.substr(input.length()-8);
×
NEW
1037
            unsigned int part1{0};
×
NEW
1038
            unsigned int part2{0};
×
NEW
1039
            unsigned int part3{0};
×
NEW
1040
            unsigned int part4{0};
×
NEW
1041
            if (sscanf(last8.c_str(), "%02x%02x%02x%02x", &part1, &part2, &part3, &part4) == 4) {
×
NEW
1042
              ComboAddress address(std::to_string(part1) + "." + std::to_string(part2) + "." + std::to_string(part3) + "." + std::to_string(part4));
×
NEW
1043
              return address.toString();
×
NEW
1044
            }
×
1045
          }
×
1046
        }
×
NEW
1047
        return allZerosIP;
×
NEW
1048
      } catch (const PDNSException &e) {
×
NEW
1049
        return allZerosIP;
×
1050
      }
×
1051
    });
×
1052

1053
  lua.writeFunction("createForward6", []() {
×
1054
      static string allZerosIP{"::"};
×
NEW
1055
      try {
×
NEW
1056
        DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
NEW
1057
        if (!record_name.isWildcard()) {
×
NEW
1058
          return allZerosIP;
×
1059
        }
×
NEW
1060
        record_name.chopOff();
×
NEW
1061
        DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
1062

NEW
1063
        auto parts = rel.getRawLabels();
×
NEW
1064
        if (parts.size() == 8) {
×
NEW
1065
          string tot;
×
NEW
1066
          for (int chunk = 0; chunk < 8; ++chunk) {
×
NEW
1067
            if (chunk != 0) {
×
NEW
1068
              tot.append(1, ':');
×
NEW
1069
              }
×
NEW
1070
            tot += parts.at(chunk);
×
NEW
1071
          }
×
NEW
1072
          ComboAddress address(tot);
×
NEW
1073
          return address.toString();
×
NEW
1074
        }
×
NEW
1075
        if (parts.size() == 1) {
×
NEW
1076
          if (parts[0].find('-') != std::string::npos) {
×
NEW
1077
            std::replace(parts[0].begin(), parts[0].end(), '-', ':');
×
NEW
1078
            ComboAddress address(parts[0]);
×
NEW
1079
            return address.toString();
×
NEW
1080
          }
×
1081
          if (parts[0].size() >= 32) {
×
1082
            auto ippart = parts[0].substr(parts[0].size()-32);
×
1083
            auto fulladdress =
×
1084
              ippart.substr(0, 4) + ":" +
×
1085
              ippart.substr(4, 4) + ":" +
×
1086
              ippart.substr(8, 4) + ":" +
×
1087
              ippart.substr(12, 4) + ":" +
×
1088
              ippart.substr(16, 4) + ":" +
×
1089
              ippart.substr(20, 4) + ":" +
×
1090
              ippart.substr(24, 4) + ":" +
×
1091
              ippart.substr(28, 4);
×
1092

NEW
1093
            ComboAddress address(fulladdress);
×
NEW
1094
            return address.toString();
×
1095
          }
×
1096
        }
×
NEW
1097
        return allZerosIP;
×
NEW
1098
      } catch (const PDNSException &e) {
×
NEW
1099
        return allZerosIP;
×
UNCOV
1100
      }
×
UNCOV
1101
    });
×
NEW
1102
  lua.writeFunction("createReverse6", [](const string &format, boost::optional<std::unordered_map<string,string>> excp){
×
1103
      vector<ComboAddress> candidates;
×
1104

1105
      try {
×
1106
        auto labels= s_lua_record_ctx->qname.getRawLabels();
×
NEW
1107
        if (labels.size()<32) {
×
1108
          return std::string("unknown");
×
NEW
1109
        }
×
1110
        boost::format fmt(format);
×
1111
        fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit )  );
×
1112

1113

1114
        string together;
×
1115
        vector<string> quads;
×
NEW
1116
        for (int chunk = 0; chunk < 8; ++chunk) {
×
NEW
1117
          if (chunk != 0) {
×
NEW
1118
            together += ":";
×
NEW
1119
          }
×
1120
          string lquad;
×
NEW
1121
          for (int quartet = 0; quartet < 4; ++quartet) {
×
NEW
1122
            lquad.append(1, labels[31 - chunk * 4 - quartet][0]);
×
NEW
1123
            together += labels[31 - chunk * 4 - quartet][0];
×
1124
          }
×
1125
          quads.push_back(lquad);
×
1126
        }
×
NEW
1127
        ComboAddress ip6(together,0);
×
1128

NEW
1129
        if (excp) {
×
NEW
1130
          auto& addrs=*excp;
×
UNCOV
1131
          for(const auto& addr: addrs) {
×
1132
            // this makes sure we catch all forms of the address
NEW
1133
            if (ComboAddress(addr.first, 0) == ip6) {
×
1134
              return addr.second;
×
NEW
1135
            }
×
1136
          }
×
1137
        }
×
1138

1139
        string dashed=ip6.toString();
×
1140
        std::replace(dashed.begin(), dashed.end(), ':', '-');
×
1141

1142
        // https://github.com/PowerDNS/pdns/issues/7524
1143
        if (boost::ends_with(dashed, "-")) {
×
1144
          // "a--a-" -> "a--a-0"
1145
          dashed.push_back('0');
×
1146
        }
×
1147
        if (boost::starts_with(dashed, "-") || dashed.compare(2, 2, "--") == 0) {
×
1148
          // "-a--a" -> "0-a--a"               "aa--a" -> "0aa--a"
1149
          dashed.insert(0, "0");
×
1150
        }
×
1151

NEW
1152
        for (int byte = 31; byte >= 0; --byte) {
×
NEW
1153
          fmt % labels[byte];
×
NEW
1154
        }
×
UNCOV
1155
        fmt % dashed;
×
1156

NEW
1157
        for(const auto& lquad : quads) {
×
1158
          fmt % lquad;
×
NEW
1159
        }
×
1160

1161
        return fmt.str();
×
1162
      }
×
1163
      catch(std::exception& ex) {
×
1164
        g_log<<Logger::Error<<"Lua record exception: "<<ex.what()<<endl;
×
1165
      }
×
1166
      catch(PDNSException& ex) {
×
1167
        g_log<<Logger::Error<<"Lua record exception: "<<ex.reason<<endl;
×
1168
      }
×
1169
      return std::string("unknown");
×
1170
    });
×
1171

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

1175
      if (nmg.match(ComboAddress(address))) {
×
1176
        return {address};
×
1177
      } else {
×
1178
        if (fallback) {
×
1179
          if (fallback->empty()) {
×
1180
            // if fallback is an empty string, return an empty array
1181
            return {};
×
1182
          }
×
1183
          return {*fallback};
×
1184
        }
×
1185

1186
        if (ca.isIPv4()) {
×
1187
          return {string("0.0.0.0")};
×
1188
        } else {
×
1189
          return {string("::")};
×
1190
        }
×
1191
      }
×
1192
    });
×
1193

1194
  /*
1195
   * Simplistic test to see if an IP address listens on a certain port
1196
   * Will return a single IP address from the set of available IP addresses. If
1197
   * no IP address is available, will return a random element of the set of
1198
   * addresses supplied for testing.
1199
   *
1200
   * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
1201
   */
1202
  lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, const boost::optional<std::unordered_map<string,string>> options) {
×
1203
    port = std::max(port, 0);
×
1204
    port = std::min(port, static_cast<int>(std::numeric_limits<uint16_t>::max()));
×
1205

1206
    auto checker = [](const ComboAddress& addr, const opts_t& opts) {
×
1207
      return g_up.isUp(addr, opts);
×
1208
    };
×
1209
    return genericIfUp(ips, options, checker, port);
×
1210
  });
×
1211

1212
  lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) {
×
1213
      vector<ComboAddress> candidates;
×
1214
      opts_t opts;
×
1215
      if(options)
×
1216
        opts = *options;
×
1217

1218
      ComboAddress ca_unspec;
×
1219
      ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1220

1221
      // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1222
      for (const auto& [count, unitmap] : ipurls) {
×
1223
        // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1224
        vector<ComboAddress> available;
×
1225

1226
        for (const auto& [ipStr, url] : unitmap) {
×
1227
          // unit: ["192.0.2.1"] = "https://example.com"
1228
          ComboAddress ip(ipStr);
×
1229
          candidates.push_back(ip);
×
1230
          if (g_up.isUp(ca_unspec, url, opts)) {
×
1231
            available.push_back(ip);
×
1232
          }
×
1233
        }
×
1234
        if(!available.empty()) {
×
1235
          vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
1236
          return convComboAddressListToString(res);
×
1237
        }
×
1238
      }
×
1239

1240
      // All units down, apply backupSelector on all candidates
1241
      vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
×
1242
      return convComboAddressListToString(res);
×
1243
    });
×
1244

1245
  lua.writeFunction("ifurlup", [](const std::string& url,
×
1246
                                          const boost::variant<iplist_t, ipunitlist_t>& ips,
×
1247
                                          boost::optional<opts_t> options) {
×
1248

1249
    auto checker = [&url](const ComboAddress& addr, const opts_t& opts) {
×
1250
        return g_up.isUp(addr, url, opts);
×
1251
      };
×
1252
      return genericIfUp(ips, options, checker);
×
1253
    });
×
1254
  /*
1255
   * Returns a random IP address from the supplied list
1256
   * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
1257
   */
1258
  lua.writeFunction("pickrandom", [](const iplist_t& ips) {
×
1259
      vector<string> items = convStringList(ips);
×
1260
      return pickRandom<string>(items);
×
1261
    });
×
1262

1263
  /*
1264
   * Based on the hash of `bestwho`, returns an IP address from the list
1265
   * supplied, weighted according to the results of isUp calls.
1266
   * @example pickselfweighted('http://example.com/weight', { "192.0.2.20", "203.0.113.4", "203.0.113.2" })
1267
   */
1268
  lua.writeFunction("pickselfweighted", [](const std::string& url,
×
1269
                                             const iplist_t& ips,
×
1270
                                             boost::optional<opts_t> options) {
×
1271
      vector< pair<int, ComboAddress> > items;
×
1272
      opts_t opts;
×
1273
      if(options) {
×
1274
        opts = *options;
×
1275
      }
×
1276

1277
      items.reserve(ips.capacity());
×
1278
      bool available = false;
×
1279

1280
      vector<ComboAddress> conv = convComboAddressList(ips);
×
1281
      for (auto& entry : conv) {
×
1282
        int weight = 0;
×
1283
        weight = g_up.isUp(entry, url, opts);
×
1284
        if(weight>0) {
×
1285
          available = true;
×
1286
        }
×
1287
        items.emplace_back(weight, entry);
×
1288
      }
×
1289
      if(available) {
×
1290
        return pickWeightedHashed<ComboAddress>(s_lua_record_ctx->bestwho, items).toString();
×
1291
      }
×
1292

1293
      // All units down, apply backupSelector on all candidates
1294
      return pickWeightedRandom<ComboAddress>(items).toString();
×
1295
    });
×
1296

1297
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
×
1298
      vector<string> items = convStringList(ips);
×
1299
          return pickRandomSample<string>(n, items);
×
1300
    });
×
1301

1302
  lua.writeFunction("pickhashed", [](const iplist_t& ips) {
×
1303
      vector<string> items = convStringList(ips);
×
1304
      return pickHashed<string>(s_lua_record_ctx->bestwho, items);
×
1305
    });
×
1306
  /*
1307
   * Returns a random IP address from the supplied list, as weighted by the
1308
   * various ``weight`` parameters
1309
   * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1310
   */
1311
  lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
×
1312
      vector< pair<int, string> > items = convIntStringPairList(ips);
×
1313
      return pickWeightedRandom<string>(items);
×
1314
    });
×
1315

1316
  /*
1317
   * Based on the hash of `bestwho`, returns an IP address from the list
1318
   * supplied, as weighted by the various `weight` parameters
1319
   * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1320
   */
1321
  lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) {
×
1322
      vector< pair<int, string> > items;
×
1323

1324
      items.reserve(ips.size());
×
1325
      for (auto& entry : ips) {
×
1326
        items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
×
1327
      }
×
1328

1329
      return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1330
    });
×
1331

1332
  /*
1333
   * Based on the hash of the record name, return an IP address from the list
1334
   * supplied, as weighted by the various `weight` parameters
1335
   * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1336
   */
1337
  lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t > ips) {
×
1338
      vector< pair<int, string> > items;
×
1339

1340
      items.reserve(ips.size());
×
1341
      for(auto& i : ips)
×
1342
      {
×
1343
        items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
×
1344
      }
×
1345

1346
      return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
×
1347
    });
×
1348
  /*
1349
   * Based on the hash of `bestwho`, returns an IP address from the list
1350
   * supplied, as weighted by the various `weight` parameters and distributed consistently
1351
   * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1352
   */
1353
  lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) {
×
1354
    std::vector<std::pair<int, std::string>> items;
×
1355

1356
    items.reserve(ips.size());
×
1357
    for (const auto& entry : ips) {
×
1358
      items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
×
1359
    }
×
1360

1361
    return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1362
  });
×
1363

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

1367
      return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
×
1368

1369
    });
×
1370

1371
  if (g_luaRecordExecLimit > 0) {
×
1372
      lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1373
  }
×
1374

1375
  lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){
×
1376
      throw std::runtime_error("Script took too long");
×
1377
    });
×
1378

1379
  lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
×
1380
    return getGeo(ip, attr);
×
1381
  });
×
1382
  lua.writeVariable("GeoIPQueryAttribute", std::unordered_map<std::string, int>{{"ASn", GeoIPInterface::GeoIPQueryAttribute::ASn}, {"City", GeoIPInterface::GeoIPQueryAttribute::City}, {"Continent", GeoIPInterface::GeoIPQueryAttribute::Continent}, {"Country", GeoIPInterface::GeoIPQueryAttribute::Country}, {"Country2", GeoIPInterface::GeoIPQueryAttribute::Country2}, {"Name", GeoIPInterface::GeoIPQueryAttribute::Name}, {"Region", GeoIPInterface::GeoIPQueryAttribute::Region}, {"Location", GeoIPInterface::GeoIPQueryAttribute::Location}});
×
1383

1384
  typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
×
1385

1386
  lua.writeFunction("asnum", [](const combovar_t& asns) {
×
1387
      string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
×
1388
      return doCompare(asns, res, [](const std::string& a, const std::string& b) {
×
1389
          return !strcasecmp(a.c_str(), b.c_str());
×
1390
        });
×
1391
    });
×
1392
  lua.writeFunction("continent", [](const combovar_t& continent) {
×
1393
     string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1394
      return doCompare(continent, res, [](const std::string& a, const std::string& b) {
×
1395
          return !strcasecmp(a.c_str(), b.c_str());
×
1396
        });
×
1397
    });
×
1398
  lua.writeFunction("continentCode", []() {
×
1399
      string unknown("unknown");
×
1400
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1401
      if ( res == unknown ) {
×
1402
       return std::string("--");
×
1403
      }
×
1404
      return res;
×
1405
    });
×
1406
  lua.writeFunction("country", [](const combovar_t& var) {
×
1407
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1408
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1409
          return !strcasecmp(a.c_str(), b.c_str());
×
1410
        });
×
1411

1412
    });
×
1413
  lua.writeFunction("countryCode", []() {
×
1414
      string unknown("unknown");
×
1415
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1416
      if ( res == unknown ) {
×
1417
       return std::string("--");
×
1418
      }
×
1419
      return res;
×
1420
    });
×
1421
  lua.writeFunction("region", [](const combovar_t& var) {
×
1422
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1423
      return doCompare(var, res, [](const std::string& a, const std::string& b) {
×
1424
          return !strcasecmp(a.c_str(), b.c_str());
×
1425
        });
×
1426

1427
    });
×
1428
  lua.writeFunction("regionCode", []() {
×
1429
      string unknown("unknown");
×
1430
      string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1431
      if ( res == unknown ) {
×
1432
       return std::string("--");
×
1433
      }
×
1434
      return res;
×
1435
    });
×
1436
  lua.writeFunction("netmask", [](const iplist_t& ips) {
×
1437
      for(const auto& i :ips) {
×
1438
        Netmask nm(i.second);
×
1439
        if(nm.match(s_lua_record_ctx->bestwho))
×
1440
          return true;
×
1441
      }
×
1442
      return false;
×
1443
    });
×
1444
  /* {
1445
       {
1446
        {'192.168.0.0/16', '10.0.0.0/8'},
1447
        {'192.168.20.20', '192.168.20.21'}
1448
       },
1449
       {
1450
        {'0.0.0.0/0'}, {'192.0.2.1'}
1451
       }
1452
     }
1453
  */
1454
  lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) {
×
1455
      for(const auto& rule : in) {
×
1456
        const auto& netmasks=rule.second[0].second;
×
1457
        const auto& destinations=rule.second[1].second;
×
1458
        for(const auto& nmpair : netmasks) {
×
1459
          Netmask nm(nmpair.second);
×
1460
          if(nm.match(s_lua_record_ctx->bestwho)) {
×
1461
            if (destinations.empty()) {
×
1462
              throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm.toString() + ")");
×
1463
            }
×
1464
            return destinations[dns_random(destinations.size())].second;
×
1465
          }
×
1466
        }
×
1467
      }
×
1468
      return std::string();
×
1469
    });
×
1470

1471
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
×
1472
      vector<string> result;
×
1473
          result.reserve(ips.size());
×
1474

1475
      for(const auto& ip : ips) {
×
1476
          result.emplace_back(ip.second);
×
1477
      }
×
1478
      if(result.empty()) {
×
1479
        throw std::invalid_argument("The IP list cannot be empty");
×
1480
      }
×
1481
      return result;
×
1482
    });
×
1483

1484
  lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) {
×
1485
    DNSName rec;
×
1486
    vector<string> ret;
×
1487
    try {
×
1488
      rec = DNSName(record);
×
1489
    }
×
1490
    catch (const std::exception& e) {
×
1491
      g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
×
1492
      return ret;
×
1493
    }
×
1494
    try {
×
1495
      SOAData soaData;
×
1496

1497
      if (!getAuth(rec, qtype, &soaData)) {
×
1498
        return ret;
×
1499
      }
×
1500

1501
      vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id);
×
1502
      for (const auto& drec : drs) {
×
1503
        ret.push_back(drec.dr.getContent()->getZoneRepresentation());
×
1504
      }
×
1505
    }
×
1506
    catch (std::exception& e) {
×
1507
      g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
×
1508
    }
×
1509
    return ret;
×
1510
  });
×
1511

1512
  lua.writeFunction("include", [&lua](string record) {
×
1513
      DNSName rec;
×
1514
      try {
×
1515
        rec = DNSName(record) + s_lua_record_ctx->zone;
×
1516
      } catch (const std::exception &e){
×
1517
        g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl;
×
1518
        return;
×
1519
      }
×
1520
      try {
×
1521
        vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zone_record.domain_id);
×
1522
        for(const auto& dr : drs) {
×
1523
          auto lr = getRR<LUARecordContent>(dr.dr);
×
1524
          lua.executeCode(lr->getCode());
×
1525
        }
×
1526
      }
×
1527
      catch(std::exception& e) {
×
1528
        g_log<<Logger::Error<<"Failed to load include record for Lua record "<<rec<<": "<<e.what()<<endl;
×
1529
      }
×
1530
    });
×
1531
}
×
1532

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

1541
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1542

1543
  LuaContext& lua = *LUA->getLua();
×
1544

1545
  s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1546
  s_lua_record_ctx->qname = query;
×
1547
  s_lua_record_ctx->zone_record = zone_record;
×
1548
  s_lua_record_ctx->zone = zone;
×
1549

1550
  lua.writeVariable("qname", query);
×
1551
  lua.writeVariable("zone", zone);
×
1552
  lua.writeVariable("zoneid", zone_record.domain_id);
×
1553
  lua.writeVariable("who", dnsp.getInnerRemote());
×
1554
  lua.writeVariable("localwho", dnsp.getLocal());
×
1555
  lua.writeVariable("dh", (dnsheader*)&dnsp.d);
×
1556
  lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
×
1557
  lua.writeVariable("tcp", dnsp.d_tcp);
×
1558
  lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
×
1559
  if(dnsp.hasEDNSSubnet()) {
×
1560
    lua.writeVariable("ecswho", dnsp.getRealRemote());
×
1561
    s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
×
1562
  }
×
1563
  else {
×
1564
    lua.writeVariable("ecswho", nullptr);
×
1565
    s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
×
1566
  }
×
1567
  lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
×
1568

1569
  try {
×
1570
    string actual;
×
1571
    if(!code.empty() && code[0]!=';')
×
1572
      actual = "return " + code;
×
1573
    else
×
1574
      actual = code.substr(1);
×
1575

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

1578
    vector<string> contents;
×
1579
    if(auto str = boost::get<string>(&content))
×
1580
      contents.push_back(*str);
×
1581
    else
×
1582
      for(const auto& c : boost::get<vector<pair<int,string>>>(content))
×
1583
        contents.push_back(c.second);
×
1584

1585
    for(const auto& content_it: contents) {
×
1586
      if(qtype==QType::TXT)
×
1587
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
×
1588
      else
×
1589
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
×
1590
    }
×
1591
  } catch(std::exception &e) {
×
1592
    g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
×
1593
    try {
×
1594
      std::rethrow_if_nested(e);
×
1595
      g_log<<endl;
×
1596
    } catch(const std::exception& ne) {
×
1597
      g_log << ": " << ne.what() << std::endl;
×
1598
    }
×
1599
    catch(const PDNSException& ne) {
×
1600
      g_log << ": " << ne.reason << std::endl;
×
1601
    }
×
1602
    throw ;
×
1603
  }
×
1604

1605
  return ret;
×
1606
}
×
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