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

PowerDNS / pdns / 20618548088

31 Dec 2025 12:00PM UTC coverage: 72.648% (-0.7%) from 73.336%
20618548088

Pull #16693

github

web-flow
Merge 3f7d9a75b into 65de281db
Pull Request #16693: auth: plumbing for structured logging

39009 of 65430 branches covered (59.62%)

Branch coverage included in aggregate %.

807 of 2400 new or added lines in 58 files covered. (33.63%)

200 existing lines in 39 files now uncovered.

129187 of 166092 relevant lines covered (77.78%)

5266744.49 hits per line

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

0.17
/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
    std::shared_ptr<Logr::Logger> slog;
58
    bool operator<(const CheckDesc& rhs) const
59
    {
×
60
      std::map<string,string> oopts, rhsoopts;
×
61
      for(const auto& m : opts)
×
62
        oopts[m.first]=m.second;
×
63
      for(const auto& m : rhs.opts)
×
64
        rhsoopts[m.first]=m.second;
×
65

66
      return std::tuple(rem, url, oopts) <
×
67
        std::tuple(rhs.rem, rhs.url, rhsoopts);
×
68
    }
×
69
  };
70
  struct CheckState
71
  {
72
    CheckState(time_t _lastAccess): lastAccess(_lastAccess) {}
×
73
    /* current status */
74
    std::atomic<bool> status{false};
75
    /* current weight */
76
    std::atomic<int> weight{0};
77
    /* first check? */
78
    std::atomic<bool> first{true};
79
    /* number of successive checks returning failure */
80
    std::atomic<unsigned int> failures{0};
81
    /* last time the status was accessed */
82
    std::atomic<time_t> lastAccess{0};
83
    /* last time the status was modified */
84
    std::atomic<time_t> lastStatusUpdate{0};
85
  };
86

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

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

103
    string remstring;
×
104
    try {
×
105
      int timeout = 2;
×
106
      if (cd.opts.count("timeout")) {
×
107
        timeout = std::atoi(cd.opts.at("timeout").c_str());
×
108
      }
×
109
      string useragent = productName();
×
110
      if (cd.opts.count("useragent")) {
×
111
        useragent = cd.opts.at("useragent");
×
112
      }
×
113
      size_t byteslimit = 0;
×
114
      if (cd.opts.count("byteslimit")) {
×
115
        byteslimit = static_cast<size_t>(std::atoi(cd.opts.at("byteslimit").c_str()));
×
116
      }
×
117
      int http_code = 200;
×
118
      if (cd.opts.count("httpcode") != 0) {
×
119
        http_code = pdns::checked_stoi<int>(cd.opts.at("httpcode"));
×
120
      }
×
121

122
      MiniCurl minicurl(useragent, false);
×
123

124
      string content;
×
125
      const ComboAddress* rem = nullptr;
×
126
      if(cd.rem.sin4.sin_family != AF_UNSPEC) {
×
127
        rem = &cd.rem;
×
128
        remstring = rem->toString();
×
129
      } else {
×
130
        remstring = "[externally checked IP]";
×
131
      }
×
132

133
      if (cd.opts.count("source")) {
×
134
        ComboAddress src(cd.opts.at("source"));
×
135
        content=minicurl.getURL(cd.url, rem, &src, timeout, false, false, byteslimit, http_code);
×
136
      }
×
137
      else {
×
138
        content=minicurl.getURL(cd.url, rem, nullptr, timeout, false, false, byteslimit, http_code);
×
139
      }
×
140
      if (cd.opts.count("stringmatch") && content.find(cd.opts.at("stringmatch")) == string::npos) {
×
141
        throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % cd.opts.at("stringmatch")));
×
142
      }
×
143

144
      int weight = 0;
×
145
      try {
×
146
        weight = stoi(content);
×
147
        if(!status) {
×
NEW
148
          SLOG(g_log<<Logger::Info<<"Lua record monitoring declaring "<<remstring<<" UP for URL "<<cd.url<<"!"<<" with WEIGHT "<<content<<"!"<<endl,
×
NEW
149
               cd.slog->info(Logr::Info, "Lua record monitoring declares url UP", "ip", Logging::Loggable(remstring), "url", Logging::Loggable(cd.url), "weight", Logging::Loggable(content)));
×
150
        }
×
151
      }
×
152
      catch (const std::exception&) {
×
153
        if(!status) {
×
NEW
154
          SLOG(g_log<<Logger::Info<<"Lua record monitoring declaring "<<remstring<<" UP for URL "<<cd.url<<"!"<<endl,
×
NEW
155
               cd.slog->info(Logr::Info, "Lua record monitoring declares url UP", "ip", Logging::Loggable(remstring), "url", Logging::Loggable(cd.url)));
×
156
        }
×
157
      }
×
158

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

231
          if (desc.opts.count("interval") != 0) {
×
232
            checkInterval = std::atoi(desc.opts.at("interval").c_str());
×
233
            if (checkInterval != 0) {
×
234
              interval = std::gcd(interval, checkInterval);
×
235
            }
×
236
          }
×
237

238
          if (not state->first) {
×
239
            time_t nextCheckSecond = state->lastStatusUpdate;
×
240
            if (checkInterval != 0) {
×
241
               nextCheckSecond += checkInterval;
×
242
            }
×
243
            else {
×
244
               nextCheckSecond += g_luaHealthChecksInterval;
×
245
            }
×
246
            if (checkStart < std::chrono::system_clock::from_time_t(nextCheckSecond)) {
×
247
              continue; // too early
×
248
            }
×
249
          }
×
250

251
          if (desc.url.empty()) { // TCP
×
252
            results.push_back(std::async(std::launch::async, &IsUpOracle::checkTCP, this, desc, state->status.load(), state->first.load()));
×
253
          } else { // URL
×
254
            results.push_back(std::async(std::launch::async, &IsUpOracle::checkURL, this, desc, state->status.load(), state->first.load()));
×
255
          }
×
256
          // Give it a chance to run at least once.
257
          // If minimumFailures * interval > lua-health-checks-expire-delay, then a down status will never get reported.
258
          // This is unlikely to be a problem in practice due to the default value of the expire delay being one hour.
259
          if (not state->first &&
×
260
              lastAccess < (checkStart - std::chrono::seconds(g_luaHealthChecksExpireDelay))) {
×
261
            toDelete.push_back(desc);
×
262
          }
×
263
        }
×
264
      }
×
265
      // we can release the lock as nothing will be deleted
266
      for (auto& future: results) {
×
267
        future.wait();
×
268
      }
×
269
      if (!toDelete.empty()) {
×
270
        auto statuses = d_statuses.write_lock();
×
271
        for (auto& it: toDelete) {
×
272
          statuses->erase(it);
×
273
        }
×
274
      }
×
275

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

279
      std::this_thread::sleep_until(checkStart + std::chrono::seconds(interval));
×
280
    }
×
281
  }
×
282

283
  typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
284
  SharedLockGuarded<statuses_t> d_statuses;
285

286
  std::unique_ptr<std::thread> d_checkerThread;
287
  std::atomic_flag d_checkerThreadStarted;
288

289
  void setStatus(const CheckDesc& cd, bool status)
290
  {
×
291
    auto statuses = d_statuses.write_lock();
×
292
    auto& state = (*statuses)[cd];
×
293
    state->lastStatusUpdate = time(nullptr);
×
294
    state->first = false;
×
295
    if (status) {
×
296
      state->failures = 0;
×
297
      state->status = true;
×
298
    } else {
×
299
      unsigned int minimumFailures = 1;
×
300
      if (cd.opts.count("minimumFailures") != 0) {
×
301
        unsigned int value = std::atoi(cd.opts.at("minimumFailures").c_str());
×
302
        if (value != 0) {
×
303
          minimumFailures = std::max(minimumFailures, value);
×
304
        }
×
305
      }
×
306
      // Since `status' was set to false at constructor time, we need to
307
      // recompute its value unconditionally to expose "down, but not enough
308
      // times yet" targets as up.
309
      state->status = ++state->failures < minimumFailures;
×
310
    }
×
311
  }
×
312

313
  //NOLINTNEXTLINE(readability-identifier-length)
314
  void setWeight(const CheckDesc& cd, int weight){
×
315
    auto statuses = d_statuses.write_lock();
×
316
    auto& state = (*statuses)[cd];
×
317
    state->weight = weight;
×
318
  }
×
319

320
  void setDown(const CheckDesc& cd)
321
  {
×
322
    setStatus(cd, false);
×
323
  }
×
324

325
  void setUp(const CheckDesc& cd)
326
  {
×
327
    setStatus(cd, true);
×
328
  }
×
329
};
330

331
// The return value of this function can be one of three sets of values:
332
// - positive integer: the target is up, the return value is its weight.
333
//   (1 if weights are not used)
334
// - zero: the target is down.
335
// - negative integer: the check for this target has not completed yet.
336
//   (this value is only reported if the failOnIncompleteCheck option is
337
//    set, otherwise zero will be returned)
338
//NOLINTNEXTLINE(readability-identifier-length)
339
int IsUpOracle::isUp(const CheckDesc& cd)
340
{
×
341
  if (!d_checkerThreadStarted.test_and_set()) {
×
342
    d_checkerThread = std::make_unique<std::thread>([this] { return checkThread(); });
×
343
  }
×
344
  time_t now = time(nullptr);
×
345
  {
×
346
    auto statuses = d_statuses.read_lock();
×
347
    auto iter = statuses->find(cd);
×
348
    if (iter != statuses->end()) {
×
349
      iter->second->lastAccess = now;
×
350
      if (iter->second->weight > 0) {
×
351
        return iter->second->weight;
×
352
      }
×
353
      return static_cast<int>(iter->second->status);
×
354
    }
×
355
  }
×
356
  // try to parse options so we don't insert any malformed content
357
  if (cd.opts.count("source")) {
×
358
    ComboAddress src(cd.opts.at("source"));
×
359
  }
×
360
  {
×
361
    auto statuses = d_statuses.write_lock();
×
362
    // Make sure we don't insert new entry twice now we have the lock
363
    if (statuses->find(cd) == statuses->end()) {
×
364
      (*statuses)[cd] = std::make_unique<CheckState>(now);
×
365
    }
×
366
  }
×
367
  // If explicitly asked to fail on incomplete checks, report this (as
368
  // a negative value).
369
  static const std::string foic{"failOnIncompleteCheck"};
×
370
  if (cd.opts.count(foic) != 0) {
×
371
    if (cd.opts.at(foic) == "true") {
×
372
      return -1;
×
373
    }
×
374
  }
×
375
  return 0;
×
376
}
×
377

378
int IsUpOracle::isUp(std::shared_ptr<Logr::Logger> slog, const ComboAddress& remote, const opts_t& opts)
379
{
×
NEW
380
  CheckDesc cd{remote, "", opts, slog};
×
381
  return isUp(cd);
×
382
}
×
383

384
int IsUpOracle::isUp(std::shared_ptr<Logr::Logger> slog, const ComboAddress& remote, const std::string& url, const opts_t& opts)
385
{
×
NEW
386
  CheckDesc cd{remote, url, opts, slog};
×
387
  return isUp(cd);
×
388
}
×
389

390
IsUpOracle g_up;
391
namespace {
392
template<typename T, typename C>
393
bool doCompare(const T& var, const std::string& res, const C& cmp)
394
{
×
395
  if(auto country = boost::get<string>(&var))
×
396
    return cmp(*country, res);
×
397

398
  auto countries=boost::get<vector<pair<int,string> > >(&var);
×
399
  for(const auto& country : *countries) {
×
400
    if(cmp(country.second, res))
×
401
      return true;
×
402
  }
×
403
  return false;
×
404
}
×
405
}
406

407
static std::string getGeo(std::shared_ptr<Logr::Logger> slog, const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
408
{
×
409
  static bool initialized;
×
410
  extern std::function<std::string(const std::string& ip, int)> g_getGeo;
×
411
  if(!g_getGeo) {
×
412
    if(!initialized) {
×
NEW
413
      SLOG(g_log<<Logger::Error<<"Lua record attempted to use GeoIPBackend functionality, but backend not launched"<<endl,
×
NEW
414
           slog->info(Logr::Error, "Lua record attempted to use GeoIPBackend functionality, but backend not launched"));
×
415
      initialized=true;
×
416
    }
×
417
    return "unknown";
×
418
  }
×
419
  else
×
420
    return g_getGeo(ip, (int)qa);
×
421
}
×
422

423
template <typename T>
424
static T pickRandom(const vector<T>& items)
425
{
×
426
  if (items.empty()) {
×
427
    throw std::invalid_argument("The items list cannot be empty");
×
428
  }
×
429
  return items[dns_random(items.size())];
×
430
}
×
431

432
template <typename T>
433
static T pickHashed(const ComboAddress& who, const vector<T>& items)
434
{
×
435
  if (items.empty()) {
×
436
    throw std::invalid_argument("The items list cannot be empty");
×
437
  }
×
438
  ComboAddress::addressOnlyHash aoh;
×
439
  return items[aoh(who) % items.size()];
×
440
}
×
441

442
template <typename T>
443
static T pickWeightedRandom(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.emplace_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
  int r = dns_random(sum);
×
462
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
463
  return p->second;
×
464
}
×
465

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

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

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

485
  ComboAddress::addressOnlyHash aoh;
×
486
  int r = aoh(bestwho) % 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 T pickWeightedNameHashed(const DNSName& dnsname, vector< pair<int, T> >& items)
493
{
×
494
  if (items.empty()) {
×
495
    throw std::invalid_argument("The items list cannot be empty");
×
496
  }
×
497
  size_t sum=0;
×
498
  vector< pair<int, T> > pick;
×
499
  pick.reserve(items.size());
×
500

501
  for(auto& i : items) {
×
502
    sum += i.first;
×
503
    pick.push_back({sum, i.second});
×
504
  }
×
505

506
  if (sum == 0) {
×
507
    throw std::invalid_argument("The sum of items cannot be zero");
×
508
  }
×
509

510
  size_t r = dnsname.hash() % sum;
×
511
  auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
×
512
  return p->second;
×
513
}
×
514

515
template <typename T>
516
static vector<T> pickRandomSample(int n, const vector<T>& items)
517
{
×
518
  if (items.empty()) {
×
519
    throw std::invalid_argument("The items list cannot be empty");
×
520
  }
×
521

522
  vector<T> pick;
×
523
  pick.reserve(items.size());
×
524

525
  for(auto& item : items) {
×
526
    pick.push_back(item);
×
527
  }
×
528

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

531
  if (count == 0) {
×
532
    return vector<T>();
×
533
  }
×
534

535
  std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
×
536

537
  vector<T> result = {pick.begin(), pick.begin() + count};
×
538
  return result;
×
539
}
×
540

541
static bool getLatLon(std::shared_ptr<Logr::Logger> slog, const std::string& ip, double& lat, double& lon)
542
{
×
NEW
543
  string inp = getGeo(slog, ip, GeoIPInterface::Location);
×
544
  if(inp.empty())
×
545
    return false;
×
546
  lat=atof(inp.c_str());
×
547
  auto pos=inp.find(' ');
×
548
  if(pos != string::npos)
×
549
    lon=atof(inp.c_str() + pos);
×
550
  return true;
×
551
}
×
552

553
static bool getLatLon(std::shared_ptr<Logr::Logger> slog, const std::string& ip, string& loc)
554
{
×
555
  int latdeg, latmin, londeg, lonmin;
×
556
  double latsec, lonsec;
×
557
  char lathem='X', lonhem='X';
×
558

559
  double lat = 0, lon = 0;
×
NEW
560
  if(!getLatLon(slog, ip, lat, lon))
×
561
    return false;
×
562

563
  if(lat > 0) {
×
564
    lathem='N';
×
565
  }
×
566
  else {
×
567
    lat = -lat;
×
568
    lathem='S';
×
569
  }
×
570

571
  if(lon > 0) {
×
572
    lonhem='E';
×
573
  }
×
574
  else {
×
575
    lon = -lon;
×
576
    lonhem='W';
×
577
  }
×
578

579
  latdeg = lat;
×
580
  latmin = (lat - latdeg)*60.0;
×
581
  latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
×
582

583
  londeg = lon;
×
584
  lonmin = (lon - londeg)*60.0;
×
585
  lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
×
586

587
  // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
588

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

591
  loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
×
592
  return true;
×
593
}
×
594

595
static ComboAddress pickclosest(std::shared_ptr<Logr::Logger> slog, const ComboAddress& bestwho, const vector<ComboAddress>& wips)
596
{
×
597
  if (wips.empty()) {
×
598
    throw std::invalid_argument("The IP list cannot be empty");
×
599
  }
×
600
  map<double, vector<ComboAddress> > ranked;
×
601
  double wlat=0, wlon=0;
×
NEW
602
  getLatLon(slog, bestwho.toString(), wlat, wlon);
×
603
  //        cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
604
  vector<string> ret;
×
605
  for(const auto& c : wips) {
×
606
    double lat=0, lon=0;
×
NEW
607
    getLatLon(slog, c.toString(), lat, lon);
×
608
    //          cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
609
    double latdiff = wlat-lat;
×
610
    double londiff = wlon-lon;
×
611
    if(londiff > 180)
×
612
      londiff = 360 - londiff;
×
613
    double dist2=latdiff*latdiff + londiff*londiff;
×
614
    //          cout<<"    distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
615
    ranked[dist2].push_back(c);
×
616
  }
×
617
  return ranked.begin()->second[dns_random(ranked.begin()->second.size())];
×
618
}
×
619

620
static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, domainid_t zoneid)
621
{
×
622
  static LockGuarded<UeberBackend> s_ub;
×
623

624
  DNSZoneRecord dr;
×
625
  vector<DNSZoneRecord> ret;
×
626
  {
×
627
    auto ub = s_ub.lock();
×
628
    ub->lookup(QType(qtype), name, zoneid);
×
629
    while (ub->get(dr)) {
×
630
      ret.push_back(dr);
×
631
    }
×
632
  }
×
633
  return ret;
×
634
}
×
635

636
static bool getAuth(const ZoneName& name, uint16_t qtype, SOAData* soaData, Netmask remote)
637
{
×
638
  static LockGuarded<UeberBackend> s_ub;
×
639

640
  {
×
641
    auto ueback = s_ub.lock();
×
642
    return ueback->getAuth(name, qtype, soaData, remote);
×
643
  }
×
644
}
×
645

646
static std::string getOptionValue(const boost::optional<opts_t>& options, const std::string &name, const std::string &defaultValue)
647
{
×
648
  string selector=defaultValue;
×
649
  if(options) {
×
650
    if(options->count(name))
×
651
      selector=options->find(name)->second;
×
652
  }
×
653
  return selector;
×
654
}
×
655

656
static vector<ComboAddress> useSelector(std::shared_ptr<Logr::Logger> slog, const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
657
{
×
658
  vector<ComboAddress> ret;
×
659

660
  if(selector=="all")
×
661
    return candidates;
×
662
  else if(selector=="empty")
×
663
    return ret;
×
664
  else if(selector=="random")
×
665
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
666
  else if(selector=="pickclosest")
×
NEW
667
    ret.emplace_back(pickclosest(slog, bestwho, candidates));
×
668
  else if(selector=="hashed")
×
669
    ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates));
×
670
  else {
×
NEW
671
    SLOG(g_log<<Logger::Warning<<"Lua record called with unknown selector '"<<selector<<"'"<<endl,
×
NEW
672
         slog->info(Logr::Warning, "Lua record called with unknown selector", "selector", Logging::Loggable(selector)));
×
673
    ret.emplace_back(pickRandom<ComboAddress>(candidates));
×
674
  }
×
675

676
  return ret;
×
677
}
×
678

679
static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
680
{
×
681
  vector<string> result;
×
682
  result.reserve(items.size());
×
683

684
  for (const auto& item : items) {
×
685
    result.emplace_back(item.toString());
×
686
  }
×
687

688
  return result;
×
689
}
×
690

691
static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
692
{
×
693
  vector<ComboAddress> result;
×
694
  result.reserve(items.size());
×
695

696
  for(const auto& item : items) {
×
697
    result.emplace_back(ComboAddress(item.second, port));
×
698
  }
×
699

700
  return result;
×
701
}
×
702

703
/**
704
 * Reads and unify single or multiple sets of ips :
705
 * - {'192.0.2.1', '192.0.2.2'}
706
 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
707
 */
708

709
static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
710
{
×
711
  vector<vector<ComboAddress>> candidates;
×
712

713
  if(auto simple = boost::get<iplist_t>(&items)) {
×
714
    vector<ComboAddress> unit = convComboAddressList(*simple, port);
×
715
    candidates.push_back(unit);
×
716
  } else {
×
717
    auto units = boost::get<ipunitlist_t>(items);
×
718
    for(const auto& u : units) {
×
719
      vector<ComboAddress> unit = convComboAddressList(u.second, port);
×
720
      candidates.push_back(unit);
×
721
    }
×
722
  }
×
723
  return candidates;
×
724
}
×
725

726
static vector<string> convStringList(const iplist_t& items)
727
{
×
728
  vector<string> result;
×
729
  result.reserve(items.size());
×
730

731
  for(const auto& item : items) {
×
732
    result.emplace_back(item.second);
×
733
  }
×
734

735
  return result;
×
736
}
×
737

738
static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
739
{
×
740
  vector<pair<int,string> > result;
×
741
  result.reserve(items.size());
×
742

743
  for(const auto& item : items) {
×
744
    result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
×
745
  }
×
746

747
  return result;
×
748
}
×
749

750
bool g_LuaRecordSharedState;
751

752
typedef struct AuthLuaRecordContext
753
{
754
  ComboAddress          bestwho;
755
  DNSName               qname;
756
  DNSZoneRecord         zone_record;
757
  DNSName               zone;
758
  Netmask               remote;
759
} lua_record_ctx_t;
760

761
static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
762

763
/*
764
 *  Holds computed hashes for a given entry
765
 */
766
struct EntryHashesHolder
767
{
768
  std::atomic<size_t> weight;
769
  std::string entry;
770
  SharedLockGuarded<std::vector<unsigned int>> hashes;
771
  std::atomic<time_t> lastUsed;
772

773
  EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
×
774
  }
×
775

776
  bool hashesComputed() {
×
777
    return weight == hashes.read_lock()->size();
×
778
  }
×
779
  void hash() {
×
780
    auto locked = hashes.write_lock();
×
781
    locked->clear();
×
782
    locked->reserve(weight);
×
783
    size_t count = 0;
×
784
    while (count < weight) {
×
785
      auto value = boost::str(boost::format("%s-%d") % entry % count);
×
786
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
787
      auto whash = burtle(reinterpret_cast<const unsigned char*>(value.data()), value.size(), 0);
×
788
      locked->push_back(whash);
×
789
      ++count;
×
790
    }
×
791
    std::sort(locked->begin(), locked->end());
×
792
  }
×
793
};
794

795
using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
796

797
static SharedLockGuarded<std::map<
798
  zone_hashes_key_t, // zoneid qname entry
799
  std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
800
  >>
801
s_zone_hashes;
802

803
static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
804

805
/**
806
 * every ~g_luaConsistentHashesCleanupInterval, do a cleanup to delete entries that haven't been used in the last g_luaConsistentHashesExpireDelay
807
 */
808
static void cleanZoneHashes()
809
{
×
810
  auto now = time(nullptr);
×
811
  if (s_lastConsistentHashesCleanup > (now - g_luaConsistentHashesCleanupInterval)) {
×
812
    return ;
×
813
  }
×
814
  s_lastConsistentHashesCleanup = now;
×
815
  std::vector<zone_hashes_key_t> toDelete{};
×
816
  {
×
817
    auto locked = s_zone_hashes.read_lock();
×
818
    auto someTimeAgo = now - g_luaConsistentHashesExpireDelay;
×
819

820
    for (const auto& [key, entry]: *locked) {
×
821
      if (entry->lastUsed > someTimeAgo) {
×
822
        toDelete.push_back(key);
×
823
      }
×
824
    }
×
825
  }
×
826
  if (!toDelete.empty()) {
×
827
    auto wlocked = s_zone_hashes.write_lock();
×
828
    for (const auto& key : toDelete) {
×
829
      wlocked->erase(key);
×
830
    }
×
831
  }
×
832
}
×
833

834
static std::vector<std::shared_ptr<EntryHashesHolder>> getCHashedEntries(const domainid_t zoneId, const std::string& queryName, const std::vector<std::pair<int, std::string>>& items)
835
{
×
836
  std::vector<std::shared_ptr<EntryHashesHolder>> result{};
×
837
  std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
×
838

839
  {
×
840
    time_t now = time(nullptr);
×
841
    auto locked = s_zone_hashes.read_lock();
×
842

843
    for (const auto& [weight, entry]: items) {
×
844
      auto key = std::make_tuple(zoneId, queryName, entry);
×
845
      if (locked->count(key) == 0) {
×
846
        newEntries[key] = std::make_shared<EntryHashesHolder>(weight, entry, now);
×
847
      } else {
×
848
        locked->at(key)->weight = weight;
×
849
        locked->at(key)->lastUsed = now;
×
850
        result.push_back(locked->at(key));
×
851
      }
×
852
    }
×
853
  }
×
854
  if (!newEntries.empty()) {
×
855
    auto wlocked = s_zone_hashes.write_lock();
×
856

857
    for (auto& [key, entry]: newEntries) {
×
858
      result.push_back(entry);
×
859
      (*wlocked)[key] = std::move(entry);
×
860
    }
×
861
  }
×
862

863
  return result;
×
864
}
×
865

866
static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
867
{
×
868
  const auto& zoneId = s_lua_record_ctx->zone_record.domain_id;
×
869
  const auto queryName = s_lua_record_ctx->qname.toString();
×
870
  unsigned int sel = std::numeric_limits<unsigned int>::max();
×
871
  unsigned int min = std::numeric_limits<unsigned int>::max();
×
872

873
  boost::optional<std::string> ret;
×
874
  boost::optional<std::string> first;
×
875

876
  cleanZoneHashes();
×
877

878
  auto entries = getCHashedEntries(zoneId, queryName, items);
×
879

880
  ComboAddress::addressOnlyHash addrOnlyHash;
×
881
  auto qhash = addrOnlyHash(bestwho);
×
882
  for (const auto& entry : entries) {
×
883
    if (!entry->hashesComputed()) {
×
884
      entry->hash();
×
885
    }
×
886
    {
×
887
      const auto hashes = entry->hashes.read_lock();
×
888
      if (!hashes->empty()) {
×
889
        if (min > *(hashes->begin())) {
×
890
          min = *(hashes->begin());
×
891
          first = entry->entry;
×
892
        }
×
893

894
        auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
×
895
        if (hash_it != hashes->end()) {
×
896
          if (*hash_it < sel) {
×
897
            sel = *hash_it;
×
898
            ret = entry->entry;
×
899
          }
×
900
        }
×
901
      }
×
902
    }
×
903
  }
×
904
  if (ret != boost::none) {
×
905
    return *ret;
×
906
  }
×
907
  if (first != boost::none) {
×
908
    return *first;
×
909
  }
×
910
  return {};
×
911
}
×
912

913
static vector<string> genericIfUp(std::shared_ptr<Logr::Logger> slog, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options, const std::function<int(std::shared_ptr<Logr::Logger>, const ComboAddress&, const opts_t&)>& upcheckf, uint16_t port = 0)
914
{
×
915
  vector<vector<ComboAddress> > candidates;
×
916
  opts_t opts;
×
917
  if (options) {
×
918
    opts = *options;
×
919
  }
×
920

921
  candidates = convMultiComboAddressList(ips, port);
×
922

923
  bool incompleteCheck{true};
×
924
  for(const auto& unit : candidates) {
×
925
    vector<ComboAddress> available;
×
926
    for(const auto& address : unit) {
×
NEW
927
      int status = upcheckf(slog, address, opts);
×
928
      if (status > 0) {
×
929
        available.push_back(address);
×
930
      }
×
931
      if (status >= 0) {
×
932
        incompleteCheck = false;
×
933
      }
×
934
    }
×
935
    if(!available.empty()) {
×
NEW
936
      vector<ComboAddress> res = useSelector(slog, getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
937
      return convComboAddressListToString(res);
×
938
    }
×
939
  }
×
940

941
  // All units down or have not completed their checks yet.
942
  if (incompleteCheck) {
×
943
    throw std::runtime_error("if{url,port}up health check has not completed yet");
×
944
  }
×
945

946
  // Apply backupSelector on all candidates
947
  vector<ComboAddress> ret{};
×
948
  for(const auto& unit : candidates) {
×
949
    ret.insert(ret.end(), unit.begin(), unit.end());
×
950
  }
×
951

NEW
952
  vector<ComboAddress> res = useSelector(slog, getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
×
953
  return convComboAddressListToString(res);
×
954
}
×
955

956
// Lua functions available to the user
957

958
static string lua_latlon(std::shared_ptr<Logr::Logger> slog)
959
{
×
960
  double lat{0};
×
961
  double lon{0};
×
NEW
962
  getLatLon(slog, s_lua_record_ctx->bestwho.toString(), lat, lon);
×
963
  return std::to_string(lat)+" "+std::to_string(lon);
×
964
}
×
965

966
static string lua_latlonloc(std::shared_ptr<Logr::Logger> slog)
967
{
×
968
  string loc;
×
NEW
969
  getLatLon(slog, s_lua_record_ctx->bestwho.toString(), loc);
×
970
  return loc;
×
971
}
×
972

973
static string lua_closestMagic(std::shared_ptr<Logr::Logger> slog)
974
{
×
975
  vector<ComboAddress> candidates;
×
976
  // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
977
  for (auto label : s_lua_record_ctx->qname.getRawLabels()) {
×
978
    std::replace(label.begin(), label.end(), '-', '.');
×
979
    try {
×
980
      candidates.emplace_back(label);
×
981
    } catch (const PDNSException& exc) {
×
982
      // no need to continue as we most likely reached the end of the ip list
983
      break ;
×
984
    }
×
985
  }
×
NEW
986
  return pickclosest(slog, s_lua_record_ctx->bestwho, candidates).toString();
×
987
}
×
988

989
static string lua_latlonMagic(std::shared_ptr<Logr::Logger> slog)
990
{
×
991
  auto labels = s_lua_record_ctx->qname.getRawLabels();
×
992
  if (labels.size() < 4) {
×
993
    return {"unknown"};
×
994
  }
×
995
  double lat{0};
×
996
  double lon{0};
×
NEW
997
  getLatLon(slog, labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
×
998
  return std::to_string(lat)+" "+std::to_string(lon);
×
999
}
×
1000

1001
static string lua_createReverse(std::shared_ptr<Logr::Logger> slog, const string &format, boost::optional<opts_t> exceptions)
1002
{
×
1003
  try {
×
1004
    auto labels = s_lua_record_ctx->qname.getRawLabels();
×
1005
    if (labels.size() < 4) {
×
1006
      return {"unknown"};
×
1007
    }
×
1008

1009
    // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
1010
    // exceptions["1.2.3.4"]="bert.powerdns.com" then provides an exception
1011
    if (exceptions) {
×
1012
      ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
×
1013
      const auto& uom = *exceptions;
×
1014
      for (const auto& address : uom) {
×
1015
        if(ComboAddress(address.first, 0) == req) {
×
1016
          return address.second;
×
1017
        }
×
1018
      }
×
1019
    }
×
1020
    boost::format fmt(format);
×
1021
    fmt.exceptions(boost::io::all_error_bits ^ (boost::io::too_many_args_bit | boost::io::too_few_args_bit));
×
1022
    fmt % labels[3] % labels[2] % labels[1] % labels[0];
×
1023

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

1026
    boost::format fmt2("%02x%02x%02x%02x");
×
1027
    for (int i = 3; i >= 0; --i) {
×
1028
      fmt2 % atoi(labels[i].c_str());
×
1029
    }
×
1030

1031
    fmt % (fmt2.str());
×
1032

1033
    return fmt.str();
×
1034
  }
×
1035
  catch(std::exception& ex) {
×
NEW
1036
    SLOG(g_log<<Logger::Error<<"createReverse error: "<<ex.what()<<endl,
×
NEW
1037
         slog->error(Logr::Error, ex.what(), "Lua record exception in createReverse"));
×
1038
  }
×
1039
  catch (const PDNSException &e) {
×
NEW
1040
    SLOG(g_log<<Logger::Error<<"createReverse error: "<<e.reason<<endl,
×
NEW
1041
         slog->error(Logr::Error, e.reason, "Lua record exception in createReverse"));
×
1042
  }
×
1043
  return {"error"};
×
1044
}
×
1045

1046
static string lua_createForward()
1047
{
×
1048
  static string allZerosIP{"0.0.0.0"};
×
1049
  try {
×
1050
    DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
1051
    if (!record_name.isWildcard()) {
×
1052
      return allZerosIP;
×
1053
    }
×
1054
    record_name.chopOff();
×
1055
    DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
1056

1057
    // parts is something like ["1", "2", "3", "4", "static"] or
1058
    // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
1059
    auto parts = rel.getRawLabels();
×
1060
    // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
1061
    if (parts.size() >= 4) {
×
1062
      ComboAddress address(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
×
1063
      return address.toString();
×
1064
    }
×
1065
    if (!parts.empty()) {
×
1066
      auto& input = parts.at(0);
×
1067

1068
      // allow a word without - in front, as long as it does not contain anything that could be a number
1069
      size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
×
1070
      if (nonhexprefix > 0) {
×
1071
        input = input.substr(nonhexprefix);
×
1072
      }
×
1073

1074
      // either hex string, or 12-13-14-15
1075
      vector<string> ip_parts;
×
1076

1077
      stringtok(ip_parts, input, "-");
×
1078
      if (ip_parts.size() >= 4) {
×
1079
        // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
1080
        string ret;
×
1081
        for (size_t index=4; index > 0; index--) {
×
1082
          auto octet = ip_parts.at(ip_parts.size() - index);
×
1083
          auto octetVal = std::stol(octet); // may throw
×
1084
          if (octetVal >= 0 && octetVal <= 255) {
×
1085
            ret += octet + ".";
×
1086
          } else {
×
1087
            return allZerosIP;
×
1088
          }
×
1089
        }
×
1090
        ret.resize(ret.size() - 1); // remove trailing dot after last octet
×
1091
        return ret;
×
1092
      }
×
1093
      if (input.length() >= 8) {
×
1094
        auto last8 = input.substr(input.length()-8);
×
1095
        unsigned int part1{0};
×
1096
        unsigned int part2{0};
×
1097
        unsigned int part3{0};
×
1098
        unsigned int part4{0};
×
1099
        if (sscanf(last8.c_str(), "%02x%02x%02x%02x", &part1, &part2, &part3, &part4) == 4) {
×
1100
          ComboAddress address(std::to_string(part1) + "." + std::to_string(part2) + "." + std::to_string(part3) + "." + std::to_string(part4));
×
1101
          return address.toString();
×
1102
        }
×
1103
      }
×
1104
    }
×
1105
    return allZerosIP;
×
1106
  } catch (const PDNSException &) {
×
1107
    return allZerosIP;
×
1108
  } catch (const std::exception &) { // thrown by std::stol
×
1109
    return allZerosIP;
×
1110
  }
×
1111
}
×
1112

1113
static string lua_createForward6()
1114
{
×
1115
   static string allZerosIP{"::"};
×
1116
   try {
×
1117
     DNSName record_name{s_lua_record_ctx->zone_record.dr.d_name};
×
1118
     if (!record_name.isWildcard()) {
×
1119
       return allZerosIP;
×
1120
     }
×
1121
     record_name.chopOff();
×
1122
     DNSName rel{s_lua_record_ctx->qname.makeRelative(record_name)};
×
1123

1124
     auto parts = rel.getRawLabels();
×
1125
     if (parts.size() == 8) {
×
1126
       string tot;
×
1127
       for (int chunk = 0; chunk < 8; ++chunk) {
×
1128
         if (chunk != 0) {
×
1129
           tot.append(1, ':');
×
1130
         }
×
1131
        tot += parts.at(chunk);
×
1132
      }
×
1133
      ComboAddress address(tot);
×
1134
      return address.toString();
×
1135
    }
×
1136
    if (parts.size() == 1) {
×
1137
      if (parts[0].find('-') != std::string::npos) {
×
1138
        std::replace(parts[0].begin(), parts[0].end(), '-', ':');
×
1139
        ComboAddress address(parts[0]);
×
1140
        return address.toString();
×
1141
      }
×
1142
      if (parts[0].size() >= 32) {
×
1143
        auto ippart = parts[0].substr(parts[0].size()-32);
×
1144
        auto fulladdress =
×
1145
          ippart.substr(0, 4) + ":" +
×
1146
          ippart.substr(4, 4) + ":" +
×
1147
          ippart.substr(8, 4) + ":" +
×
1148
          ippart.substr(12, 4) + ":" +
×
1149
          ippart.substr(16, 4) + ":" +
×
1150
          ippart.substr(20, 4) + ":" +
×
1151
          ippart.substr(24, 4) + ":" +
×
1152
          ippart.substr(28, 4);
×
1153

1154
        ComboAddress address(fulladdress);
×
1155
        return address.toString();
×
1156
      }
×
1157
    }
×
1158
    return allZerosIP;
×
1159
  } catch (const PDNSException &e) {
×
1160
    return allZerosIP;
×
1161
  }
×
1162
}
×
1163

1164
static string lua_createReverse6(std::shared_ptr<Logr::Logger> slog, const string &format, boost::optional<opts_t> exceptions)
1165
{
×
1166
  try {
×
1167
    auto labels= s_lua_record_ctx->qname.getRawLabels();
×
1168
    if (labels.size()<32) {
×
1169
      return {"unknown"};
×
1170
    }
×
1171

1172
    boost::format fmt(format);
×
1173
    fmt.exceptions(boost::io::all_error_bits ^ (boost::io::too_many_args_bit | boost::io::too_few_args_bit));
×
1174

1175
    string together;
×
1176
    vector<string> quads;
×
1177
    for (int chunk = 0; chunk < 8; ++chunk) {
×
1178
      if (chunk != 0) {
×
1179
        together += ":";
×
1180
      }
×
1181
      string lquad;
×
1182
      for (int quartet = 0; quartet < 4; ++quartet) {
×
1183
        lquad.append(1, labels[31 - chunk * 4 - quartet][0]);
×
1184
        together += labels[31 - chunk * 4 - quartet][0];
×
1185
      }
×
1186
      quads.push_back(std::move(lquad));
×
1187
    }
×
1188
    ComboAddress ip6(together,0);
×
1189

1190
    if (exceptions) {
×
1191
      auto& addrs=*exceptions;
×
1192
      for(const auto& addr: addrs) {
×
1193
        // this makes sure we catch all forms of the address
1194
        if (ComboAddress(addr.first, 0) == ip6) {
×
1195
          return addr.second;
×
1196
        }
×
1197
      }
×
1198
    }
×
1199

1200
    string dashed=ip6.toString();
×
1201
    std::replace(dashed.begin(), dashed.end(), ':', '-');
×
1202

1203
    // https://github.com/PowerDNS/pdns/issues/7524
1204
    if (boost::ends_with(dashed, "-")) {
×
1205
      // "a--a-" -> "a--a-0"
1206
      dashed.push_back('0');
×
1207
    }
×
1208
    if (boost::starts_with(dashed, "-") || dashed.compare(2, 2, "--") == 0) {
×
1209
      // "-a--a" -> "0-a--a"               "aa--a" -> "0aa--a"
1210
      dashed.insert(0, "0");
×
1211
    }
×
1212

1213
    for (int byte = 31; byte >= 0; --byte) {
×
1214
      fmt % labels[byte];
×
1215
    }
×
1216
    fmt % dashed;
×
1217

1218
    for(const auto& lquad : quads) {
×
1219
      fmt % lquad;
×
1220
    }
×
1221

1222
    return fmt.str();
×
1223
  }
×
1224
  catch(std::exception& ex) {
×
NEW
1225
    SLOG(g_log<<Logger::Error<<"createReverse6 exception: "<<ex.what()<<endl,
×
NEW
1226
         slog->error(Logr::Error, ex.what(), "Lua record exception in createReverse6"));
×
1227
  }
×
1228
  catch(PDNSException& ex) {
×
NEW
1229
    SLOG(g_log<<Logger::Error<<"createReverse6 exception: "<<ex.reason<<endl,
×
NEW
1230
         slog->error(Logr::Error, ex.reason, "Lua record exception in createReverse6"));
×
1231
  }
×
1232
  return {"error"};
×
1233
}
×
1234

1235
static vector<string> lua_filterForward(const string& address, NetmaskGroup& nmg, boost::optional<string> fallback)
1236
{
×
1237
  ComboAddress caddr(address);
×
1238

1239
  if (nmg.match(ComboAddress(address))) {
×
1240
    return {address};
×
1241
  }
×
1242
  if (fallback) {
×
1243
    if (fallback->empty()) {
×
1244
      // if fallback is an empty string, return an empty array
1245
      return {};
×
1246
    }
×
1247
    return {*fallback};
×
1248
  }
×
1249

1250
  if (caddr.isIPv4()) {
×
1251
    return {string("0.0.0.0")};
×
1252
  }
×
1253
  return {"::"};
×
1254
}
×
1255

1256
/*
1257
 * Simplistic test to see if an IP address listens on a certain port
1258
 * Will return a single IP address from the set of available IP addresses. If
1259
 * no IP address is available, will return a random element of the set of
1260
 * addresses supplied for testing.
1261
 *
1262
 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
1263
 */
1264
static vector<string> lua_ifportup(std::shared_ptr<Logr::Logger> slog, int port, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options)
1265
{
×
1266
  port = std::max(port, 0);
×
1267
  port = std::min(port, static_cast<int>(std::numeric_limits<uint16_t>::max()));
×
1268

NEW
1269
  auto checker = [](std::shared_ptr<Logr::Logger> log, const ComboAddress& addr, const opts_t& opts) -> int {
×
NEW
1270
    return g_up.isUp(log, addr, opts);
×
1271
  };
×
NEW
1272
  return genericIfUp(slog, ips, std::move(options), checker, port);
×
1273
}
×
1274

1275
static vector<string> lua_ifurlextup(std::shared_ptr<Logr::Logger> slog, const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options)
1276
{
×
1277
  vector<ComboAddress> candidates;
×
1278
  opts_t opts;
×
1279
  if (options) {
×
1280
    opts = *options;
×
1281
  }
×
1282

1283
  ComboAddress ca_unspec;
×
1284
  ca_unspec.sin4.sin_family=AF_UNSPEC;
×
1285

1286
  // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1287
  bool incompleteCheck{true};
×
1288
  for (const auto& [count, unitmap] : ipurls) {
×
1289
    // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1290
    vector<ComboAddress> available;
×
1291

1292
    for (const auto& [ipStr, url] : unitmap) {
×
1293
      // unit: ["192.0.2.1"] = "https://example.com"
1294
      ComboAddress address(ipStr);
×
1295
      candidates.push_back(address);
×
NEW
1296
      int status = g_up.isUp(slog, ca_unspec, url, opts);
×
1297
      if (status > 0) {
×
1298
        available.push_back(address);
×
1299
      }
×
1300
      if (status >= 0) {
×
1301
        incompleteCheck = false;
×
1302
      }
×
1303
    }
×
1304
    if(!available.empty()) {
×
NEW
1305
      vector<ComboAddress> res = useSelector(slog, getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
×
1306
      return convComboAddressListToString(res);
×
1307
    }
×
1308
  }
×
1309

1310
  // All units down or have not completed their checks yet.
1311
  if (incompleteCheck) {
×
1312
    throw std::runtime_error("ifexturlup health check has not completed yet");
×
1313
  }
×
1314

1315
  // Apply backupSelector on all candidates
NEW
1316
  vector<ComboAddress> res = useSelector(slog, getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
×
1317
  return convComboAddressListToString(res);
×
1318
}
×
1319

1320
static vector<string> lua_ifurlup(std::shared_ptr<Logr::Logger> slog, const std::string& url, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options)
1321
{
×
NEW
1322
  auto checker = [&url](std::shared_ptr<Logr::Logger> log, const ComboAddress& addr, const opts_t& opts) -> int {
×
NEW
1323
    return g_up.isUp(log, addr, url, opts);
×
1324
  };
×
NEW
1325
  return genericIfUp(slog, ips, std::move(options), checker);
×
1326
}
×
1327

1328
/*
1329
 * Returns a random IP address from the supplied list
1330
 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
1331
 */
1332
static string lua_pickrandom(const iplist_t& ips)
1333
{
×
1334
  vector<string> items = convStringList(ips);
×
1335
  return pickRandom<string>(items);
×
1336
}
×
1337

1338
/*
1339
 * Based on the hash of `bestwho`, returns an IP address from the list
1340
 * supplied, weighted according to the results of isUp calls.
1341
 * @example pickselfweighted('http://example.com/weight', { "192.0.2.20", "203.0.113.4", "203.0.113.2" })
1342
 */
1343
static string lua_pickselfweighted(std::shared_ptr<Logr::Logger> slog, const std::string& url, const iplist_t& ips, boost::optional<opts_t> options)
1344
{
×
1345
  vector< pair<int, ComboAddress> > items;
×
1346
  opts_t opts;
×
1347
  if(options) {
×
1348
    opts = *options;
×
1349
  }
×
1350

1351
  items.reserve(ips.capacity());
×
1352
  bool available = false;
×
1353

1354
  vector<ComboAddress> conv = convComboAddressList(ips);
×
1355
  for (auto& entry : conv) {
×
1356
    int weight = 0;
×
NEW
1357
    weight = g_up.isUp(slog, entry, url, opts);
×
1358
    if(weight>0) {
×
1359
      available = true;
×
1360
    }
×
1361
    items.emplace_back(weight, entry);
×
1362
  }
×
1363
  if(available) {
×
1364
    return pickWeightedHashed<ComboAddress>(s_lua_record_ctx->bestwho, items).toString();
×
1365
  }
×
1366

1367
  // All units down, apply backupSelector on all candidates
1368
  return pickWeightedRandom<ComboAddress>(items).toString();
×
1369
}
×
1370

1371
static vector<string> lua_pickrandomsample(int n, const iplist_t& ips)
1372
{
×
1373
  vector<string> items = convStringList(ips);
×
1374
  return pickRandomSample<string>(n, items);
×
1375
}
×
1376

1377
static string lua_pickhashed(const iplist_t& ips)
1378
{
×
1379
  vector<string> items = convStringList(ips);
×
1380
  return pickHashed<string>(s_lua_record_ctx->bestwho, items);
×
1381
}
×
1382

1383
/*
1384
 * Returns a random IP address from the supplied list, as weighted by the
1385
 * various ``weight`` parameters
1386
 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1387
 */
1388
static string lua_pickwrandom(const std::unordered_map<int, wiplist_t>& ips)
1389
{
×
1390
  vector< pair<int, string> > items = convIntStringPairList(ips);
×
1391
  return pickWeightedRandom<string>(items);
×
1392
}
×
1393

1394
/*
1395
 * Based on the hash of `bestwho`, returns an IP address from the list
1396
 * supplied, as weighted by the various `weight` parameters
1397
 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1398
 */
1399
static string lua_pickwhashed(std::unordered_map<int, wiplist_t> ips)
1400
{
×
1401
  vector< pair<int, string> > items;
×
1402

1403
  items.reserve(ips.size());
×
1404
  for (auto& entry : ips) {
×
1405
    items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
×
1406
  }
×
1407

1408
  return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
×
1409
}
×
1410

1411
/*
1412
 * Based on the hash of the record name, return an IP address from the list
1413
 * supplied, as weighted by the various `weight` parameters
1414
 * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1415
 */
1416
static string lua_picknamehashed(std::unordered_map<int, wiplist_t> ips)
1417
{
×
1418
  vector< pair<int, string> > items;
×
1419

1420
  items.reserve(ips.size());
×
1421
  for (auto& address : ips) {
×
1422
    items.emplace_back(atoi(address.second[1].c_str()), address.second[2]);
×
1423
  }
×
1424

1425
  return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
×
1426
}
×
1427

1428
/*
1429
 * Based on the hash of `bestwho`, returns an IP address from the list
1430
 * supplied, as weighted by the various `weight` parameters and distributed consistently
1431
 * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1432
 */
1433
static string lua_pickchashed(const std::unordered_map<int, wiplist_t>& ips)
1434
{
×
1435
  std::vector<std::pair<int, std::string>> items;
×
1436

1437
  items.reserve(ips.size());
×
1438
  for (const auto& entry : ips) {
×
1439
    items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
×
1440
  }
×
1441

1442
  return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
×
1443
}
×
1444

1445
static string lua_pickclosest(std::shared_ptr<Logr::Logger> slog, const iplist_t& ips)
1446
{
×
1447
  vector<ComboAddress> conv = convComboAddressList(ips);
×
1448

NEW
1449
  return pickclosest(slog, s_lua_record_ctx->bestwho, conv).toString();
×
1450
}
×
1451

1452
static void lua_report(const string& /* event */, const boost::optional<string>& /* line */)
1453
{
×
1454
  throw std::runtime_error("Script took too long");
×
1455
}
×
1456

1457
static string lua_geoiplookup(std::shared_ptr<Logr::Logger> slog, const string &address, const GeoIPInterface::GeoIPQueryAttribute attr)
1458
{
×
NEW
1459
  return getGeo(slog, address, attr);
×
1460
}
×
1461

1462
using combovar_t = const boost::variant<string,vector<pair<int,string> > >;
1463

1464
static bool lua_asnum(std::shared_ptr<Logr::Logger> slog, const combovar_t& asns)
1465
{
×
NEW
1466
  string res=getGeo(slog, s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
×
1467
  return doCompare(asns, res, [](const std::string& arg1, const std::string& arg2) -> bool {
×
1468
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
×
1469
    });
×
1470
}
×
1471

1472
static bool lua_continent(std::shared_ptr<Logr::Logger> slog, const combovar_t& continent)
1473
{
×
NEW
1474
  string res=getGeo(slog, s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1475
  return doCompare(continent, res, [](const std::string& arg1, const std::string& arg2) -> bool {
×
1476
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
×
1477
    });
×
1478
}
×
1479

1480
static string lua_continentCode(std::shared_ptr<Logr::Logger> slog)
1481
{
×
1482
  string unknown("unknown");
×
NEW
1483
  string res = getGeo(slog, s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
×
1484
  if ( res == unknown ) {
×
1485
   return {"--"};
×
1486
  }
×
1487
  return res;
×
1488
}
×
1489

1490
static bool lua_country(std::shared_ptr<Logr::Logger> slog, const combovar_t& var)
1491
{
×
NEW
1492
  string res = getGeo(slog, s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1493
  return doCompare(var, res, [](const std::string& arg1, const std::string& arg2) -> bool {
×
1494
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
×
1495
    });
×
1496

1497
}
×
1498

1499
static string lua_countryCode(std::shared_ptr<Logr::Logger> slog)
1500
{
×
1501
  string unknown("unknown");
×
NEW
1502
  string res = getGeo(slog, s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
×
1503
  if (res == unknown) {
×
1504
   return {"--"};
×
1505
  }
×
1506
  return res;
×
1507
}
×
1508

1509
static bool lua_region(std::shared_ptr<Logr::Logger> slog, const combovar_t& var)
1510
{
×
NEW
1511
  string res = getGeo(slog, s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1512
  return doCompare(var, res, [](const std::string& arg1, const std::string& arg2) -> bool {
×
1513
      return strcasecmp(arg1.c_str(), arg2.c_str()) == 0;
×
1514
    });
×
1515

1516
}
×
1517

1518
static string lua_regionCode(std::shared_ptr<Logr::Logger> slog)
1519
{
×
1520
  string unknown("unknown");
×
NEW
1521
  string res = getGeo(slog, s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
×
1522
  if ( res == unknown ) {
×
1523
   return {"--"};
×
1524
  }
×
1525
  return res;
×
1526
}
×
1527

1528
static bool lua_netmask(const iplist_t& ips)
1529
{
×
1530
  for (const auto& addr : ips) {
×
1531
    Netmask netmask(addr.second);
×
1532
    if (netmask.match(s_lua_record_ctx->bestwho)) {
×
1533
      return true;
×
1534
    }
×
1535
  }
×
1536
  return false;
×
1537
}
×
1538

1539
/* {
1540
     {
1541
      {'192.168.0.0/16', '10.0.0.0/8'},
1542
      {'192.168.20.20', '192.168.20.21'}
1543
     },
1544
     {
1545
      {'0.0.0.0/0'}, {'192.0.2.1'}
1546
     }
1547
   }
1548
*/
1549
static string lua_view(const vector<pair<int, vector<pair<int, iplist_t> > > >& pairs)
1550
{
×
1551
  for(const auto& rule : pairs) {
×
1552
    const auto& netmasks=rule.second[0].second;
×
1553
    const auto& destinations=rule.second[1].second;
×
1554
    for(const auto& nmpair : netmasks) {
×
1555
      Netmask netmask(nmpair.second);
×
1556
      if (netmask.match(s_lua_record_ctx->bestwho)) {
×
1557
        if (destinations.empty()) {
×
1558
          throw std::invalid_argument("The IP list cannot be empty (for netmask " + netmask.toString() + ")");
×
1559
        }
×
1560
        return destinations[dns_random(destinations.size())].second;
×
1561
      }
×
1562
    }
×
1563
  }
×
1564
  return {};
×
1565
}
×
1566

1567
static vector<string> lua_all(const vector< pair<int,string> >& ips)
1568
{
×
1569
  vector<string> result;
×
1570
  result.reserve(ips.size());
×
1571

1572
  for (const auto& address : ips) {
×
1573
      result.emplace_back(address.second);
×
1574
  }
×
1575
  if(result.empty()) {
×
1576
    throw std::invalid_argument("The IP list cannot be empty");
×
1577
  }
×
1578
  return result;
×
1579
}
×
1580

1581
static vector<string> lua_dblookup(std::shared_ptr<Logr::Logger> slog, const string& record, uint16_t qtype)
1582
{
×
1583
  ZoneName rec;
×
1584
  vector<string> ret;
×
1585
  try {
×
1586
    rec = ZoneName(record);
×
1587
  }
×
1588
  catch (const std::exception& e) {
×
NEW
1589
    SLOG(g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl,
×
NEW
1590
         slog->error(Logr::Error, e.what(), "DB lookup cannot be performed", "record", Logging::Loggable(record)));
×
1591
    return ret;
×
1592
  }
×
1593
  try {
×
1594
    SOAData soaData;
×
1595

1596
    if (!getAuth(rec, qtype, &soaData, s_lua_record_ctx->remote)) {
×
1597
      return ret;
×
1598
    }
×
1599

1600
    vector<DNSZoneRecord> drs = lookup(rec.operator const DNSName&(), qtype, soaData.domain_id);
×
1601
    for (const auto& drec : drs) {
×
1602
      ret.push_back(drec.dr.getContent()->getZoneRepresentation());
×
1603
    }
×
1604
  }
×
1605
  catch (std::exception& e) {
×
NEW
1606
    SLOG(g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl,
×
NEW
1607
         slog->error(Logr::Error, e.what(), "DB lookup failed", "record", Logging::Loggable(rec), "type", Logging::Loggable(qtype)));
×
1608
  }
×
1609
  return ret;
×
1610
}
×
1611

1612
static void lua_include(std::shared_ptr<Logr::Logger> slog, LuaContext& lua, const string& record)
1613
{
×
1614
  DNSName rec;
×
1615
  try {
×
1616
    rec = DNSName(record) + s_lua_record_ctx->zone;
×
1617
  } catch (const std::exception &e){
×
NEW
1618
    SLOG(g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl,
×
NEW
1619
         slog->error(Logr::Error, e.what(), "included record cannot be loaded", "record", Logging::Loggable(record)));
×
1620
    return;
×
1621
  }
×
1622
  try {
×
1623
    vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zone_record.domain_id);
×
1624
    for(const auto& zonerecord : drs) {
×
1625
      auto luarecord = getRR<LUARecordContent>(zonerecord.dr);
×
1626
      lua.executeCode(luarecord->getCode());
×
1627
    }
×
1628
  }
×
1629
  catch(std::exception& e) {
×
NEW
1630
    SLOG(g_log<<Logger::Error<<"Failed to load include record for Lua record "<<rec<<": "<<e.what()<<endl,
×
NEW
1631
         slog->error(Logr::Error, e.what(), "Failed to load included record", "record", Logging::Loggable(rec)));
×
1632
  }
×
1633
}
×
1634

1635
// Lua variables available to the user
1636

1637
static std::unordered_map<std::string, int> lua_variables{
1638
  {"ASn", GeoIPInterface::GeoIPQueryAttribute::ASn},
1639
  {"City", GeoIPInterface::GeoIPQueryAttribute::City},
1640
  {"Continent", GeoIPInterface::GeoIPQueryAttribute::Continent},
1641
  {"Country", GeoIPInterface::GeoIPQueryAttribute::Country},
1642
  {"Country2", GeoIPInterface::GeoIPQueryAttribute::Country2},
1643
  {"Name", GeoIPInterface::GeoIPQueryAttribute::Name},
1644
  {"Region", GeoIPInterface::GeoIPQueryAttribute::Region},
1645
  {"Location", GeoIPInterface::GeoIPQueryAttribute::Location}
1646
};
1647

1648
static void setupLuaRecords(std::shared_ptr<Logr::Logger> slog, LuaContext& lua)
1649
{
×
1650
  lua.writeFunction("report", [](const string& event, const boost::optional<string>& line) -> void {
×
1651
      lua_report(event, line);
×
1652
    });
×
1653

NEW
1654
  lua.writeFunction("latlon", [slog]() -> string {
×
NEW
1655
      return lua_latlon(slog);
×
1656
    });
×
NEW
1657
  lua.writeFunction("latlonloc", [slog]() -> string {
×
NEW
1658
      return lua_latlonloc(slog);
×
1659
    });
×
NEW
1660
  lua.writeFunction("closestMagic", [slog]() -> string {
×
NEW
1661
      return lua_closestMagic(slog);
×
1662
    });
×
NEW
1663
  lua.writeFunction("latlonMagic", [slog]()-> string {
×
NEW
1664
      return lua_latlonMagic(slog);
×
UNCOV
1665
    });
×
1666

1667
  lua.writeFunction("createForward", []() -> string {
×
1668
      return lua_createForward();
×
1669
    });
×
1670
  lua.writeFunction("createForward6", []() -> string {
×
1671
      return lua_createForward6();
×
1672
    });
×
1673

NEW
1674
  lua.writeFunction("createReverse", [slog](const string &format, boost::optional<opts_t> exceptions) -> string {
×
NEW
1675
      return lua_createReverse(slog, format, std::move(exceptions));
×
1676
    });
×
NEW
1677
  lua.writeFunction("createReverse6", [slog](const string &format, boost::optional<opts_t> exceptions) -> string {
×
NEW
1678
      return lua_createReverse6(slog, format, std::move(exceptions));
×
UNCOV
1679
    });
×
1680

1681
  lua.writeFunction("filterForward", [](const string& address, NetmaskGroup& nmg, boost::optional<string> fallback) -> vector<string> {
×
1682
      return lua_filterForward(address, nmg, std::move(fallback));
×
1683
    });
×
1684

NEW
1685
  lua.writeFunction("ifportup", [slog](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options) -> vector<string> {
×
NEW
1686
      return lua_ifportup(slog, port, ips, std::move(options));
×
UNCOV
1687
    });
×
1688

NEW
1689
  lua.writeFunction("ifurlextup", [slog](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) -> vector<string> {
×
NEW
1690
      return lua_ifurlextup(slog, ipurls, std::move(options));
×
UNCOV
1691
    });
×
1692

NEW
1693
  lua.writeFunction("ifurlup", [slog](const std::string& url, const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options) -> vector<string> {
×
NEW
1694
      return lua_ifurlup(slog, url, ips, std::move(options));
×
UNCOV
1695
    });
×
1696

1697
  lua.writeFunction("pickrandom", [](const iplist_t& ips) -> string {
×
1698
      return lua_pickrandom(ips);
×
1699
    });
×
1700

NEW
1701
  lua.writeFunction("pickselfweighted", [slog](const std::string& url, const iplist_t& ips, boost::optional<opts_t> options) -> string {
×
NEW
1702
      return lua_pickselfweighted(slog, url, ips, std::move(options));
×
UNCOV
1703
    });
×
1704

1705
  lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) -> vector<string> {
×
1706
      return lua_pickrandomsample(n, ips);
×
1707
    });
×
1708

1709
  lua.writeFunction("pickhashed", [](const iplist_t& ips) -> string {
×
1710
      return lua_pickhashed(ips);
×
1711
    });
×
1712
  lua.writeFunction("pickwrandom", [](const std::unordered_map<int, wiplist_t>& ips) -> string {
×
1713
      return lua_pickwrandom(ips);
×
1714
    });
×
1715

1716
  lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t> ips) -> string {
×
1717
      return lua_pickwhashed(std::move(ips));
×
1718
    });
×
1719

1720
  lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t> ips) -> string {
×
1721
      return lua_picknamehashed(std::move(ips));
×
1722
    });
×
1723
  lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) -> string {
×
1724
      return lua_pickchashed(ips);
×
1725
    });
×
1726

NEW
1727
  lua.writeFunction("pickclosest", [slog](const iplist_t& ips) -> string {
×
NEW
1728
      return lua_pickclosest(slog, ips);
×
UNCOV
1729
    });
×
1730

NEW
1731
  lua.writeFunction("geoiplookup", [slog](const string &address, const GeoIPInterface::GeoIPQueryAttribute attr) -> string {
×
NEW
1732
      return lua_geoiplookup(slog, address, attr);
×
UNCOV
1733
    });
×
1734

NEW
1735
  lua.writeFunction("asnum", [slog](const combovar_t& asns) -> bool {
×
NEW
1736
      return lua_asnum(slog, asns);
×
1737
    });
×
NEW
1738
  lua.writeFunction("continent", [slog](const combovar_t& continent) -> bool {
×
NEW
1739
      return lua_continent(slog, continent);
×
1740
    });
×
NEW
1741
  lua.writeFunction("continentCode", [slog]() -> string {
×
NEW
1742
      return lua_continentCode(slog);
×
1743
    });
×
NEW
1744
  lua.writeFunction("country", [slog](const combovar_t& var) -> bool {
×
NEW
1745
      return lua_country(slog, var);
×
1746
    });
×
NEW
1747
  lua.writeFunction("countryCode", [slog]() -> string {
×
NEW
1748
      return lua_countryCode(slog);
×
1749
    });
×
NEW
1750
  lua.writeFunction("region", [slog](const combovar_t& var) -> bool {
×
NEW
1751
      return lua_region(slog, var);
×
1752
    });
×
NEW
1753
  lua.writeFunction("regionCode", [slog]() -> string {
×
NEW
1754
      return lua_regionCode(slog);
×
1755
    });
×
1756
  lua.writeFunction("netmask", [](const iplist_t& ips) -> bool {
×
1757
      return lua_netmask(ips);
×
1758
    });
×
1759
  lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& pairs) -> string {
×
1760
      return lua_view(pairs);
×
1761
    });
×
1762

1763
  lua.writeFunction("all", [](const vector< pair<int,string> >& ips) -> vector<string> {
×
1764
      return lua_all(ips);
×
1765
    });
×
1766

NEW
1767
  lua.writeFunction("dblookup", [slog](const string& record, uint16_t qtype) -> vector<string> {
×
NEW
1768
      return lua_dblookup(slog, record, qtype);
×
UNCOV
1769
    });
×
1770

NEW
1771
  lua.writeFunction("include", [slog, &lua](const string& record) -> void {
×
NEW
1772
      lua_include(slog, lua, record);
×
UNCOV
1773
    });
×
1774

1775
  lua.writeVariable("GeoIPQueryAttribute", lua_variables);
×
1776
}
×
1777

1778
std::vector<shared_ptr<DNSRecordContent>> luaSynth(std::shared_ptr<Logr::Logger> slog, const std::string& code, const DNSName& query, const DNSZoneRecord& zone_record, const DNSName& zone, const DNSPacket& dnsp, uint16_t qtype, unique_ptr<AuthLua4>& LUA)
1779
{
×
1780
  std::vector<shared_ptr<DNSRecordContent>> ret;
×
1781

1782
  try {
×
1783
    if(!LUA ||                  // we don't have a Lua state yet
×
1784
       !g_LuaRecordSharedState) { // or we want a new one even if we had one
×
1785
      LUA = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
×
NEW
1786
      setupLuaRecords(slog, *LUA->getLua());
×
1787
    }
×
1788

1789
    LuaContext& lua = *LUA->getLua();
×
1790

1791
    s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
×
1792
    s_lua_record_ctx->qname = query;
×
1793
    s_lua_record_ctx->zone_record = zone_record;
×
1794
    s_lua_record_ctx->zone = zone;
×
1795
    s_lua_record_ctx->remote = dnsp.getRealRemote();
×
1796

1797
    lua.writeVariable("qname", query);
×
1798
    lua.writeVariable("zone", zone);
×
1799
    lua.writeVariable("zoneid", zone_record.domain_id);
×
1800
    lua.writeVariable("who", dnsp.getInnerRemote());
×
1801
    lua.writeVariable("localwho", dnsp.getLocal());
×
1802
    lua.writeVariable("dh", static_cast<const dnsheader*>(&dnsp.d));
×
1803
    lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
×
1804
    lua.writeVariable("tcp", dnsp.d_tcp);
×
1805
    lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
×
1806
    if(dnsp.hasEDNSSubnet()) {
×
1807
      lua.writeVariable("ecswho", dnsp.getRealRemote());
×
1808
      s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
×
1809
    }
×
1810
    else {
×
1811
      lua.writeVariable("ecswho", nullptr);
×
1812
      s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
×
1813
    }
×
1814
    lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
×
1815

1816
    if (g_luaRecordExecLimit > 0) {
×
1817
      lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
×
1818
    }
×
1819

1820
    string actual;
×
1821
    if(!code.empty() && code[0]!=';')
×
1822
      actual = "return " + code;
×
1823
    else
×
1824
      actual = code.substr(1);
×
1825

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

1828
    vector<string> contents;
×
1829
    if(auto str = boost::get<string>(&content))
×
1830
      contents.push_back(*str);
×
1831
    else
×
1832
      for(const auto& c : boost::get<vector<pair<int,string>>>(content))
×
1833
        contents.push_back(c.second);
×
1834

1835
    for(const auto& content_it: contents) {
×
1836
      if(qtype==QType::TXT)
×
1837
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
×
1838
      else
×
1839
        ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
×
1840
    }
×
1841
  } catch(std::exception &e) {
×
NEW
1842
    if (!g_slogStructured) {
×
NEW
1843
      g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
×
NEW
1844
    }
×
1845
    try {
×
1846
      std::rethrow_if_nested(e);
×
NEW
1847
      if (!g_slogStructured) {
×
NEW
1848
        g_log<<endl;
×
NEW
1849
      }
×
NEW
1850
      else {
×
NEW
1851
        slog->info(Logr::Info, "exception raised by Lua record", "query", Logging::Loggable(query), "type", Logging::Loggable(qtype));
×
NEW
1852
      }
×
1853
    } catch(const std::exception& ne) {
×
NEW
1854
      SLOG(g_log << ": " << ne.what() << std::endl,
×
NEW
1855
           slog->error(Logr::Info, ne.what(), "exception raised by Lua record", "query", Logging::Loggable(query), "type", Logging::Loggable(qtype)));
×
1856
    }
×
1857
    catch(const PDNSException& ne) {
×
NEW
1858
      SLOG(g_log << ": " << ne.reason << std::endl,
×
NEW
1859
           slog->error(Logr::Info, ne.reason, "exception raised by Lua record", "query", Logging::Loggable(query), "type", Logging::Loggable(qtype)));
×
1860
    }
×
1861
    throw ;
×
1862
  }
×
1863

1864
  return ret;
×
1865
}
×
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