]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/lua-record.cc
Merge pull request #14020 from omoerbeek/rec-compiling-rust-dcos
[thirdparty/pdns.git] / pdns / lua-record.cc
CommitLineData
2c010a9f 1#include <thread>
a6897a16 2#include <future>
a6897a16 3#include <boost/format.hpp>
4a0d414b 4#include <boost/uuid/string_generator.hpp>
4de01aaa 5#include <utility>
60f9d9a2 6#include <algorithm>
7#include <random>
3bf2f94d 8#include "qtype.hh"
4a0d414b 9#include <tuple>
192e15fc 10#include "version.hh"
b7edebf8 11#include "ext/luawrapper/include/LuaContext.hpp"
18cb84d3 12#include "lock.hh"
b7edebf8 13#include "lua-auth4.hh"
b7edebf8 14#include "sstuff.hh"
b7edebf8 15#include "minicurl.hh"
16#include "ueberbackend.hh"
45d4e670 17#include "dns_random.hh"
8cb70f23 18#include "auth-main.hh"
f9423419 19#include "../modules/geoipbackend/geoipinterface.hh" // only for the enum
0540d223 20
21/* to do:
25bcfaec 22 block AXFR unless TSIG, or override
69ec43c3 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
25bcfaec 31
25512599 32 unify ifurlup/ifportup
69ec43c3 33 add attribute for certificate check
0540d223 34 add list of current monitors
35 expire them too?
c9d40b1f 36
37 pool of UeberBackends?
1bc56192
CHB
38
39 Pool checks ?
0540d223 40 */
41
af68014f
CHB
42extern int g_luaRecordExecLimit;
43
1bc56192
CHB
44using iplist_t = vector<pair<int, string> >;
45using wiplist_t = std::unordered_map<int, string>;
46using ipunitlist_t = vector<pair<int, iplist_t> >;
47using opts_t = std::unordered_map<string,string>;
48
b7edebf8 49class IsUpOracle
50{
51private:
b7edebf8 52 struct CheckDesc
53 {
54 ComboAddress rem;
55 string url;
56 opts_t opts;
57 bool operator<(const CheckDesc& rhs) const
58 {
25bcfaec 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;
1bc56192 64
0b0882f5
RP
65 return std::tuple(rem, url, oopts) <
66 std::tuple(rhs.rem, rhs.url, rhsoopts);
b7edebf8 67 }
68 };
a6897a16
CHB
69 struct CheckState
70 {
71 CheckState(time_t _lastAccess): lastAccess(_lastAccess) {}
72 /* current status */
73 std::atomic<bool> status{false};
74 /* first check ? */
75 std::atomic<bool> first{true};
76 /* last time the status was accessed */
77 std::atomic<time_t> lastAccess{0};
78 };
79
b7edebf8 80public:
d27a2d30 81 IsUpOracle()
a6897a16 82 {
d27a2d30 83 d_checkerThreadStarted.clear();
a6897a16 84 }
abb11ca4 85 ~IsUpOracle() = default;
1bc56192
CHB
86 bool isUp(const ComboAddress& remote, const opts_t& opts);
87 bool isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts);
25bcfaec 88 bool isUp(const CheckDesc& cd);
1bc56192 89
b7edebf8 90private:
a6897a16
CHB
91 void checkURL(const CheckDesc& cd, const bool status, const bool first = false)
92 {
95d0df69 93 string remstring;
a6897a16 94 try {
b69ea3b5 95 int timeout = 2;
a6897a16
CHB
96 if (cd.opts.count("timeout")) {
97 timeout = std::atoi(cd.opts.at("timeout").c_str());
98 }
99 string useragent = productName();
100 if (cd.opts.count("useragent")) {
101 useragent = cd.opts.at("useragent");
102 }
0e524ab0
CHB
103 size_t byteslimit = 0;
104 if (cd.opts.count("byteslimit")) {
105 byteslimit = static_cast<size_t>(std::atoi(cd.opts.at("byteslimit").c_str()));
106 }
a6897a16 107 MiniCurl mc(useragent);
b7edebf8 108
a6897a16 109 string content;
95d0df69
PD
110 const ComboAddress* rem = nullptr;
111 if(cd.rem.sin4.sin_family != AF_UNSPEC) {
112 rem = &cd.rem;
113 remstring = rem->toString();
114 } else {
115 remstring = "[externally checked IP]";
116 }
117
a6897a16
CHB
118 if (cd.opts.count("source")) {
119 ComboAddress src(cd.opts.at("source"));
0e524ab0 120 content=mc.getURL(cd.url, rem, &src, timeout, false, false, byteslimit);
a6897a16
CHB
121 }
122 else {
0e524ab0 123 content=mc.getURL(cd.url, rem, nullptr, timeout, false, false, byteslimit);
a6897a16
CHB
124 }
125 if (cd.opts.count("stringmatch") && content.find(cd.opts.at("stringmatch")) == string::npos) {
126 throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % cd.opts.at("stringmatch")));
127 }
95d0df69 128
a6897a16 129 if(!status) {
95d0df69 130 g_log<<Logger::Info<<"LUA record monitoring declaring "<<remstring<<" UP for URL "<<cd.url<<"!"<<endl;
a6897a16
CHB
131 }
132 setUp(cd);
133 }
134 catch(std::exception& ne) {
135 if(status || first)
95d0df69 136 g_log<<Logger::Info<<"LUA record monitoring declaring "<<remstring<<" DOWN for URL "<<cd.url<<", error: "<<ne.what()<<endl;
a6897a16
CHB
137 setDown(cd);
138 }
139 }
140 void checkTCP(const CheckDesc& cd, const bool status, const bool first = false) {
141 try {
b69ea3b5 142 int timeout = 2;
a6897a16
CHB
143 if (cd.opts.count("timeout")) {
144 timeout = std::atoi(cd.opts.at("timeout").c_str());
145 }
146 Socket s(cd.rem.sin4.sin_family, SOCK_STREAM);
147 ComboAddress src;
148 s.setNonBlocking();
149 if (cd.opts.count("source")) {
150 src = ComboAddress(cd.opts.at("source"));
151 s.bind(src);
152 }
153 s.connect(cd.rem, timeout);
154 if (!status) {
e3982a50 155 g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" ";
a6897a16
CHB
156 if(cd.opts.count("source"))
157 g_log<<"(source "<<src.toString()<<") ";
158 g_log<<"UP!"<<endl;
159 }
160 setUp(cd);
161 }
162 catch (const NetworkError& ne) {
163 if(status || first) {
e3982a50 164 g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" DOWN: "<<ne.what()<<endl;
a6897a16
CHB
165 }
166 setDown(cd);
167 }
168 }
169 void checkThread()
b7edebf8 170 {
a6897a16
CHB
171 while (true)
172 {
173 std::chrono::system_clock::time_point checkStart = std::chrono::system_clock::now();
174 std::vector<std::future<void>> results;
175 std::vector<CheckDesc> toDelete;
176 {
bbbfdc22
RG
177 // make sure there's no insertion
178 auto statuses = d_statuses.read_lock();
179 for (auto& it: *statuses) {
a6897a16
CHB
180 auto& desc = it.first;
181 auto& state = it.second;
182
183 if (desc.url.empty()) { // TCP
184 results.push_back(std::async(std::launch::async, &IsUpOracle::checkTCP, this, desc, state->status.load(), state->first.load()));
185 } else { // URL
186 results.push_back(std::async(std::launch::async, &IsUpOracle::checkURL, this, desc, state->status.load(), state->first.load()));
187 }
188 if (std::chrono::system_clock::from_time_t(state->lastAccess) < (checkStart - std::chrono::seconds(g_luaHealthChecksExpireDelay))) {
189 toDelete.push_back(desc);
190 }
191 }
192 }
193 // we can release the lock as nothing will be deleted
194 for (auto& future: results) {
195 future.wait();
196 }
197 if (!toDelete.empty()) {
bbbfdc22 198 auto statuses = d_statuses.write_lock();
a6897a16 199 for (auto& it: toDelete) {
bbbfdc22 200 statuses->erase(it);
a6897a16
CHB
201 }
202 }
203 std::this_thread::sleep_until(checkStart + std::chrono::seconds(g_luaHealthChecksInterval));
204 }
205 }
b7edebf8 206
a6897a16 207 typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
bbbfdc22 208 SharedLockGuarded<statuses_t> d_statuses;
1bc56192 209
a6897a16 210 std::unique_ptr<std::thread> d_checkerThread;
37f625b0 211 std::atomic_flag d_checkerThreadStarted;
b7edebf8 212
1bc56192 213 void setStatus(const CheckDesc& cd, bool status)
b7edebf8 214 {
bbbfdc22
RG
215 auto statuses = d_statuses.write_lock();
216 auto& state = (*statuses)[cd];
a6897a16
CHB
217 state->status = status;
218 if (state->first) {
219 state->first = false;
220 }
b7edebf8 221 }
222
1bc56192 223 void setDown(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
b7edebf8 224 {
225 CheckDesc cd{rem, url, opts};
226 setStatus(cd, false);
227 }
228
1bc56192 229 void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
b7edebf8 230 {
231 CheckDesc cd{rem, url, opts};
1bc56192 232
b7edebf8 233 setStatus(cd, true);
234 }
235
236 void setDown(const CheckDesc& cd)
237 {
238 setStatus(cd, false);
239 }
240
241 void setUp(const CheckDesc& cd)
242 {
243 setStatus(cd, true);
244 }
b7edebf8 245};
246
25bcfaec 247bool IsUpOracle::isUp(const CheckDesc& cd)
b7edebf8 248{
37f625b0 249 if (!d_checkerThreadStarted.test_and_set()) {
2bbc9eb0 250 d_checkerThread = std::make_unique<std::thread>([this] { return checkThread(); });
b7edebf8 251 }
a6897a16
CHB
252 time_t now = time(nullptr);
253 {
bbbfdc22
RG
254 auto statuses = d_statuses.read_lock();
255 auto iter = statuses->find(cd);
256 if (iter != statuses->end()) {
a6897a16
CHB
257 iter->second->lastAccess = now;
258 return iter->second->status;
259 }
260 }
e3982a50
CHB
261 // try to parse options so we don't insert any malformed content
262 if (cd.opts.count("source")) {
263 ComboAddress src(cd.opts.at("source"));
264 }
a6897a16 265 {
bbbfdc22 266 auto statuses = d_statuses.write_lock();
a6897a16 267 // Make sure we don't insert new entry twice now we have the lock
bbbfdc22 268 if (statuses->find(cd) == statuses->end()) {
2bbc9eb0 269 (*statuses)[cd] = std::make_unique<CheckState>(now);
a6897a16
CHB
270 }
271 }
272 return false;
25bcfaec 273}
274
1bc56192 275bool IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts)
25bcfaec 276{
277 CheckDesc cd{remote, "", opts};
278 return isUp(cd);
b7edebf8 279}
280
1bc56192 281bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts)
b7edebf8 282{
283 CheckDesc cd{remote, url, opts};
a6897a16 284 return isUp(cd);
b7edebf8 285}
286
7d5bf0bb 287IsUpOracle g_up;
b7edebf8 288namespace {
289template<typename T, typename C>
290bool doCompare(const T& var, const std::string& res, const C& cmp)
291{
1bc56192 292 if(auto country = boost::get<string>(&var))
b7edebf8 293 return cmp(*country, res);
294
295 auto countries=boost::get<vector<pair<int,string> > >(&var);
296 for(const auto& country : *countries) {
297 if(cmp(country.second, res))
298 return true;
299 }
300 return false;
301}
302}
4a913a28 303
050e6877 304static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
b7edebf8 305{
03be9ab5 306 static bool initialized;
25bcfaec 307 extern std::function<std::string(const std::string& ip, int)> g_getGeo;
924341d2 308 if(!g_getGeo) {
309 if(!initialized) {
f5f3d7f5 310 g_log<<Logger::Error<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl;
924341d2 311 initialized=true;
312 }
313 return "unknown";
314 }
315 else
25bcfaec 316 return g_getGeo(ip, (int)qa);
4a913a28 317}
318
f52e47eb
PG
319template <typename T>
320static T pickRandom(const vector<T>& items)
4a913a28 321{
60f9d9a2 322 if (items.empty()) {
323 throw std::invalid_argument("The items list cannot be empty");
6d7e1fd3 324 }
60f9d9a2 325 return items[dns_random(items.size())];
4a913a28 326}
327
f52e47eb
PG
328template <typename T>
329static T pickHashed(const ComboAddress& who, const vector<T>& items)
60f9d9a2 330{
331 if (items.empty()) {
332 throw std::invalid_argument("The items list cannot be empty");
333 }
334 ComboAddress::addressOnlyHash aoh;
335 return items[aoh(who) % items.size()];
336}
4a913a28 337
f52e47eb
PG
338template <typename T>
339static T pickWeightedRandom(const vector< pair<int, T> >& items)
975f0839 340{
60f9d9a2 341 if (items.empty()) {
342 throw std::invalid_argument("The items list cannot be empty");
6d7e1fd3 343 }
975f0839 344 int sum=0;
f52e47eb 345 vector< pair<int, T> > pick;
60f9d9a2 346 pick.reserve(items.size());
347
348 for(auto& i : items) {
975f0839 349 sum += i.first;
e32a8d46 350 pick.emplace_back(sum, i.second);
975f0839 351 }
bb85386d 352
60f9d9a2 353 if (sum == 0) {
f52e47eb 354 throw std::invalid_argument("The sum of items cannot be zero");
6d7e1fd3 355 }
bb85386d 356
60f9d9a2 357 int r = dns_random(sum);
f52e47eb 358 auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
60f9d9a2 359 return p->second;
360}
361
f52e47eb 362template <typename T>
4a0d414b 363static T pickWeightedHashed(const ComboAddress& bestwho, const vector< pair<int, T> >& items)
60f9d9a2 364{
365 if (items.empty()) {
366 throw std::invalid_argument("The items list cannot be empty");
367 }
975f0839 368 int sum=0;
f52e47eb 369 vector< pair<int, T> > pick;
60f9d9a2 370 pick.reserve(items.size());
371
372 for(auto& i : items) {
975f0839 373 sum += i.first;
374 pick.push_back({sum, i.second});
375 }
bb85386d 376
60f9d9a2 377 if (sum == 0) {
f52e47eb 378 throw std::invalid_argument("The sum of items cannot be zero");
c7cd91db 379 }
60f9d9a2 380
975f0839 381 ComboAddress::addressOnlyHash aoh;
382 int r = aoh(bestwho) % sum;
f52e47eb 383 auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
975f0839 384 return p->second;
385}
386
d33b8bf4
BR
387template <typename T>
388static T pickWeightedNameHashed(const DNSName& dnsname, vector< pair<int, T> >& items)
389{
390 if (items.empty()) {
391 throw std::invalid_argument("The items list cannot be empty");
392 }
dc03f001 393 size_t sum=0;
d33b8bf4
BR
394 vector< pair<int, T> > pick;
395 pick.reserve(items.size());
396
397 for(auto& i : items) {
398 sum += i.first;
399 pick.push_back({sum, i.second});
400 }
401
402 if (sum == 0) {
403 throw std::invalid_argument("The sum of items cannot be zero");
404 }
405
dc03f001 406 size_t r = dnsname.hash() % sum;
d33b8bf4
BR
407 auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
408 return p->second;
409}
410
f52e47eb 411template <typename T>
bb85386d 412static vector<T> pickRandomSample(int n, const vector<T>& items)
60f9d9a2 413{
414 if (items.empty()) {
415 throw std::invalid_argument("The items list cannot be empty");
416 }
bb85386d 417
f52e47eb 418 vector<T> pick;
60f9d9a2 419 pick.reserve(items.size());
bb85386d 420
60f9d9a2 421 for(auto& item : items) {
422 pick.push_back(item);
423 }
bb85386d 424
60f9d9a2 425 int count = std::min(std::max<size_t>(0, n), items.size());
426
427 if (count == 0) {
f52e47eb 428 return vector<T>();
bb85386d 429 }
60f9d9a2 430
37b5a2a0 431 std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
bb85386d 432
f52e47eb 433 vector<T> result = {pick.begin(), pick.begin() + count};
60f9d9a2 434 return result;
435}
436
b7edebf8 437static bool getLatLon(const std::string& ip, double& lat, double& lon)
438{
f9423419 439 string inp = getGeo(ip, GeoIPInterface::Location);
b7edebf8 440 if(inp.empty())
441 return false;
442 lat=atof(inp.c_str());
443 auto pos=inp.find(' ');
444 if(pos != string::npos)
445 lon=atof(inp.c_str() + pos);
446 return true;
447}
448
0540d223 449static bool getLatLon(const std::string& ip, string& loc)
450{
451 int latdeg, latmin, londeg, lonmin;
452 double latsec, lonsec;
453 char lathem='X', lonhem='X';
1bc56192 454
b17434c6 455 double lat = 0, lon = 0;
0540d223 456 if(!getLatLon(ip, lat, lon))
457 return false;
458
459 if(lat > 0) {
460 lathem='N';
461 }
462 else {
463 lat = -lat;
464 lathem='S';
465 }
466
467 if(lon > 0) {
468 lonhem='E';
469 }
470 else {
471 lon = -lon;
472 lonhem='W';
473 }
474
0540d223 475 latdeg = lat;
476 latmin = (lat - latdeg)*60.0;
477 latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
478
479 londeg = lon;
480 lonmin = (lon - londeg)*60.0;
481 lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
482
483 // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
484
485 boost::format fmt("%d %d %d %c %d %d %d %c 0.00m 1.00m 10000.00m 10.00m");
486
487 loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
488 return true;
489}
975f0839 490
1bc56192 491static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips)
975f0839 492{
6d7e1fd3
CHB
493 if (wips.empty()) {
494 throw std::invalid_argument("The IP list cannot be empty");
495 }
60f9d9a2 496 map<double, vector<ComboAddress> > ranked;
975f0839 497 double wlat=0, wlon=0;
498 getLatLon(bestwho.toString(), wlat, wlon);
499 // cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
500 vector<string> ret;
501 for(const auto& c : wips) {
502 double lat=0, lon=0;
503 getLatLon(c.toString(), lat, lon);
504 // cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
505 double latdiff = wlat-lat;
506 double londiff = wlon-lon;
507 if(londiff > 180)
1bc56192 508 londiff = 360 - londiff;
975f0839 509 double dist2=latdiff*latdiff + londiff*londiff;
510 // cout<<" distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
511 ranked[dist2].push_back(c);
512 }
45d4e670 513 return ranked.begin()->second[dns_random(ranked.begin()->second.size())];
975f0839 514}
515
c9d40b1f 516static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid)
517{
18cb84d3
RG
518 static LockGuarded<UeberBackend> s_ub;
519
c9d40b1f 520 DNSZoneRecord dr;
521 vector<DNSZoneRecord> ret;
18cb84d3
RG
522 {
523 auto ub = s_ub.lock();
524 ub->lookup(QType(qtype), name, zoneid);
525 while (ub->get(dr)) {
526 ret.push_back(dr);
527 }
c9d40b1f 528 }
529 return ret;
530}
975f0839 531
3bf2f94d
PD
532static bool getAuth(const DNSName& name, uint16_t qtype, SOAData* soaData)
533{
534 static LockGuarded<UeberBackend> s_ub;
535
3bf2f94d
PD
536 {
537 auto ueback = s_ub.lock();
538 return ueback->getAuth(name, qtype, soaData);
539 }
540}
541
2fe7bbf8 542static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
5ca4872a 543{
2fe7bbf8 544 string selector=defaultValue;
5ca4872a 545 if(options) {
2fe7bbf8
OV
546 if(options->count(name))
547 selector=options->find(name)->second;
5ca4872a 548 }
2fe7bbf8
OV
549 return selector;
550}
551
552static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
553{
554 vector<ComboAddress> ret;
5ca4872a 555
707cedf3 556 if(selector=="all")
2fe7bbf8
OV
557 return candidates;
558 else if(selector=="random")
f52e47eb 559 ret.emplace_back(pickRandom<ComboAddress>(candidates));
1bc56192 560 else if(selector=="pickclosest")
2fe7bbf8 561 ret.emplace_back(pickclosest(bestwho, candidates));
5ca4872a 562 else if(selector=="hashed")
f52e47eb 563 ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates));
2fe7bbf8
OV
564 else {
565 g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl;
f52e47eb 566 ret.emplace_back(pickRandom<ComboAddress>(candidates));
2fe7bbf8
OV
567 }
568
569 return ret;
570}
5ca4872a 571
60f9d9a2 572static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
2fe7bbf8 573{
60f9d9a2 574 vector<string> result;
575 result.reserve(items.size());
c9768b80 576
60f9d9a2 577 for (const auto& item : items) {
578 result.emplace_back(item.toString());
c9768b80
CHB
579 }
580
60f9d9a2 581 return result;
5ca4872a 582}
583
6c8714d2 584static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
1bc56192 585{
60f9d9a2 586 vector<ComboAddress> result;
587 result.reserve(items.size());
1bc56192 588
60f9d9a2 589 for(const auto& item : items) {
6c8714d2 590 result.emplace_back(ComboAddress(item.second, port));
c9768b80 591 }
1bc56192 592
60f9d9a2 593 return result;
1bc56192
CHB
594}
595
04c53783
CHB
596/**
597 * Reads and unify single or multiple sets of ips :
598 * - {'192.0.2.1', '192.0.2.2'}
599 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
600 */
601
602static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
603{
604 vector<vector<ComboAddress>> candidates;
605
606 if(auto simple = boost::get<iplist_t>(&items)) {
607 vector<ComboAddress> unit = convComboAddressList(*simple, port);
608 candidates.push_back(unit);
609 } else {
610 auto units = boost::get<ipunitlist_t>(items);
611 for(const auto& u : units) {
612 vector<ComboAddress> unit = convComboAddressList(u.second, port);
613 candidates.push_back(unit);
614 }
615 }
616 return candidates;
617}
618
60f9d9a2 619static vector<string> convStringList(const iplist_t& items)
1bc56192 620{
60f9d9a2 621 vector<string> result;
622 result.reserve(items.size());
1bc56192 623
60f9d9a2 624 for(const auto& item : items) {
625 result.emplace_back(item.second);
c9768b80 626 }
1bc56192 627
60f9d9a2 628 return result;
629}
630
60f9d9a2 631static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
632{
633 vector<pair<int,string> > result;
634 result.reserve(items.size());
635
636 for(const auto& item : items) {
637 result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
638 }
639
640 return result;
1bc56192
CHB
641}
642
32829819 643bool g_LuaRecordSharedState;
fd1bdfb3 644
c13af350 645typedef struct AuthLuaRecordContext
b7edebf8 646{
c13af350
CHB
647 ComboAddress bestwho;
648 DNSName qname;
649 DNSName zone;
650 int zoneid;
651} lua_record_ctx_t;
1bc56192 652
c13af350 653static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
1bc56192 654
4a0d414b
CHB
655/*
656 * Holds computed hashes for a given entry
657 */
a6be268f
CHB
658struct EntryHashesHolder
659{
4a0d414b
CHB
660 std::atomic<size_t> weight;
661 std::string entry;
662 SharedLockGuarded<std::vector<unsigned int>> hashes;
a6be268f 663 std::atomic<time_t> lastUsed;
4a0d414b 664
a6be268f 665 EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) {
4a0d414b
CHB
666 }
667
668 bool hashesComputed() {
669 return weight == hashes.read_lock()->size();
670 }
671 void hash() {
672 auto locked = hashes.write_lock();
673 locked->clear();
674 locked->reserve(weight);
675 size_t count = 0;
676 while (count < weight) {
677 auto value = boost::str(boost::format("%s-%d") % entry % count);
b4608224
CHB
678 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
679 auto whash = burtle(reinterpret_cast<const unsigned char*>(value.data()), value.size(), 0);
4a0d414b
CHB
680 locked->push_back(whash);
681 ++count;
682 }
683 std::sort(locked->begin(), locked->end());
684 }
685};
686
a6be268f
CHB
687using zone_hashes_key_t = std::tuple<int, std::string, std::string>;
688
689static SharedLockGuarded<std::map<
690 zone_hashes_key_t, // zoneid qname entry
4a0d414b 691 std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes
a6be268f 692 >>
4a0d414b
CHB
693s_zone_hashes;
694
a6be268f
CHB
695static std::atomic<time_t> s_lastConsistentHashesCleanup = 0;
696
697/**
698 * every ~g_luaConsistentHashesCleanupInterval, do a cleanup to delete entries that haven't been used in the last g_luaConsistentHashesExpireDelay
699 */
700static void cleanZoneHashes()
701{
702 auto now = time(nullptr);
703 if (s_lastConsistentHashesCleanup > (now - g_luaConsistentHashesCleanupInterval)) {
704 return ;
705 }
706 s_lastConsistentHashesCleanup = now;
707 std::vector<zone_hashes_key_t> toDelete{};
708 {
709 auto locked = s_zone_hashes.read_lock();
710 auto someTimeAgo = now - g_luaConsistentHashesExpireDelay;
711
712 for (const auto& [key, entry]: *locked) {
713 if (entry->lastUsed > someTimeAgo) {
714 toDelete.push_back(key);
715 }
716 }
717 }
718 if (!toDelete.empty()) {
719 auto wlocked = s_zone_hashes.write_lock();
720 for (const auto& key : toDelete) {
721 wlocked->erase(key);
722 }
723 }
724}
725
4a0d414b
CHB
726static std::vector<std::shared_ptr<EntryHashesHolder>> getCHashedEntries(const int zoneId, const std::string& queryName, const std::vector<std::pair<int, std::string>>& items)
727{
728 std::vector<std::shared_ptr<EntryHashesHolder>> result{};
a6be268f
CHB
729 std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{};
730
731 {
732 time_t now = time(nullptr);
733 auto locked = s_zone_hashes.read_lock();
4a0d414b 734
a6be268f
CHB
735 for (const auto& [weight, entry]: items) {
736 auto key = std::make_tuple(zoneId, queryName, entry);
737 if (locked->count(key) == 0) {
738 newEntries[key] = std::make_shared<EntryHashesHolder>(weight, entry, now);
739 } else {
740 locked->at(key)->weight = weight;
741 locked->at(key)->lastUsed = now;
742 result.push_back(locked->at(key));
743 }
744 }
745 }
746 if (!newEntries.empty()) {
747 auto wlocked = s_zone_hashes.write_lock();
748
749 for (auto& [key, entry]: newEntries) {
750 result.push_back(entry);
751 (*wlocked)[key] = std::move(entry);
4a0d414b 752 }
4a0d414b
CHB
753 }
754
755 return result;
756}
757
758static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items)
759{
760 const auto& zoneId = s_lua_record_ctx->zoneid;
761 const auto queryName = s_lua_record_ctx->qname.toString();
762 unsigned int sel = std::numeric_limits<unsigned int>::max();
763 unsigned int min = std::numeric_limits<unsigned int>::max();
764
765 boost::optional<std::string> ret;
766 boost::optional<std::string> first;
767
a6be268f
CHB
768 cleanZoneHashes();
769
4a0d414b
CHB
770 auto entries = getCHashedEntries(zoneId, queryName, items);
771
772 ComboAddress::addressOnlyHash addrOnlyHash;
773 auto qhash = addrOnlyHash(bestwho);
774 for (const auto& entry : entries) {
775 if (!entry->hashesComputed()) {
776 entry->hash();
777 }
778 {
779 const auto hashes = entry->hashes.read_lock();
b4608224 780 if (!hashes->empty()) {
4a0d414b
CHB
781 if (min > *(hashes->begin())) {
782 min = *(hashes->begin());
783 first = entry->entry;
784 }
785
786 auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash);
787 if (hash_it != hashes->end()) {
788 if (*hash_it < sel) {
789 sel = *hash_it;
790 ret = entry->entry;
791 }
792 }
793 }
794 }
795 }
796 if (ret != boost::none) {
797 return *ret;
798 }
799 if (first != boost::none) {
800 return *first;
801 }
b4608224 802 return {};
4a0d414b
CHB
803}
804
224085cc
RP
805static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options, const std::function<bool(const ComboAddress&, const opts_t&)>& upcheckf, uint16_t port = 0)
806{
04c53783
CHB
807 vector<vector<ComboAddress> > candidates;
808 opts_t opts;
809 if(options)
810 opts = *options;
811
812 candidates = convMultiComboAddressList(ips, port);
813
814 for(const auto& unit : candidates) {
815 vector<ComboAddress> available;
816 for(const auto& c : unit) {
817 if (upcheckf(c, opts)) {
818 available.push_back(c);
819 }
820 }
821 if(!available.empty()) {
822 vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
823 return convComboAddressListToString(res);
824 }
825 }
826
827 // All units down, apply backupSelector on all candidates
828 vector<ComboAddress> ret{};
829 for(const auto& unit : candidates) {
830 ret.insert(ret.end(), unit.begin(), unit.end());
831 }
832
833 vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
834 return convComboAddressListToString(res);
835}
836
dc03f001 837static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cognitive-complexity)
c13af350 838{
c13af350 839 lua.writeFunction("latlon", []() {
b17434c6 840 double lat = 0, lon = 0;
c13af350 841 getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
0540d223 842 return std::to_string(lat)+" "+std::to_string(lon);
843 });
c13af350 844 lua.writeFunction("latlonloc", []() {
0540d223 845 string loc;
c13af350 846 getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
0540d223 847 return loc;
b7edebf8 848 });
c13af350 849 lua.writeFunction("closestMagic", []() {
b7edebf8 850 vector<ComboAddress> candidates;
cc5c4f6b 851 // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
c13af350 852 for(auto l : s_lua_record_ctx->qname.getRawLabels()) {
b7edebf8 853 boost::replace_all(l, "-", ".");
b7edebf8 854 try {
855 candidates.emplace_back(l);
6d7e1fd3 856 } catch (const PDNSException& e) {
cc5c4f6b
CHB
857 // no need to continue as we most likely reached the end of the ip list
858 break ;
b7edebf8 859 }
860 }
c13af350 861 return pickclosest(s_lua_record_ctx->bestwho, candidates).toString();
b7edebf8 862 });
c13af350
CHB
863 lua.writeFunction("latlonMagic", [](){
864 auto labels= s_lua_record_ctx->qname.getRawLabels();
7fedb52a 865 if(labels.size()<4)
866 return std::string("unknown");
b17434c6 867 double lat = 0, lon = 0;
7fedb52a 868 getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
869 return std::to_string(lat)+" "+std::to_string(lon);
870 });
c9d40b1f 871
1bc56192 872
54c20d39 873 lua.writeFunction("createReverse", [](string format, boost::optional<std::unordered_map<string,string>> e){
c9d40b1f 874 try {
c13af350
CHB
875 auto labels = s_lua_record_ctx->qname.getRawLabels();
876 if(labels.size()<4)
877 return std::string("unknown");
bb85386d 878
c13af350 879 vector<ComboAddress> candidates;
bb85386d 880
c13af350 881 // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
223bfcad 882 // e["1.2.3.4"]="bert.powerdns.com" then provides an exception
c13af350
CHB
883 if(e) {
884 ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
885 const auto& uom = *e;
886 for(const auto& c : uom)
887 if(ComboAddress(c.first, 0) == req)
888 return c.second;
889 }
54c20d39 890 boost::format fmt(format);
c13af350
CHB
891 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
892 fmt % labels[3] % labels[2] % labels[1] % labels[0];
bb85386d 893
c13af350 894 fmt % (labels[3]+"-"+labels[2]+"-"+labels[1]+"-"+labels[0]);
c9d40b1f 895
c13af350
CHB
896 boost::format fmt2("%02x%02x%02x%02x");
897 for(int i=3; i>=0; --i)
898 fmt2 % atoi(labels[i].c_str());
c9d40b1f 899
c13af350 900 fmt % (fmt2.str());
c9d40b1f 901
c13af350 902 return fmt.str();
c9d40b1f 903 }
af6bf47c
OM
904 catch(std::exception& ex) {
905 g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
c9d40b1f 906 }
907 return std::string("error");
908 });
c13af350 909 lua.writeFunction("createForward", []() {
448e7a2d 910 static string allZerosIP("0.0.0.0");
c13af350 911 DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
448e7a2d
PL
912 // parts is something like ["1", "2", "3", "4", "static"] or
913 // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
c9d40b1f 914 auto parts = rel.getRawLabels();
448e7a2d
PL
915 // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
916 if(parts.size()>=4) {
917 try {
918 ComboAddress ca(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
919 return ca.toString();
920 } catch (const PDNSException &e) {
921 return allZerosIP;
922 }
d3cd0635 923 } else if (!parts.empty()) {
c06dda72 924 auto& input = parts.at(0);
f7deb7d0
PD
925
926 // allow a word without - in front, as long as it does not contain anything that could be a number
f8cbdf3e 927 size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF");
f7deb7d0 928 if (nonhexprefix > 0) {
684ea0ef 929 input = input.substr(nonhexprefix);
f7deb7d0
PD
930 }
931
c9d40b1f 932 // either hex string, or 12-13-14-15
448e7a2d 933 vector<string> ip_parts;
c06dda72 934
684ea0ef 935 stringtok(ip_parts, input, "-");
df6ad4ee 936 unsigned int x1, x2, x3, x4;
448e7a2d
PL
937 if (ip_parts.size() >= 4) {
938 // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
939 string ret;
7ced3b1a
PD
940 for (size_t index=4; index > 0; index--) {
941 auto octet = ip_parts[ip_parts.size() - index];
448e7a2d
PL
942 try {
943 auto octetVal = std::stol(octet);
944 if (octetVal >= 0 && octetVal <= 255) {
7ced3b1a 945 ret += ip_parts.at(ip_parts.size() - index) + ".";
448e7a2d
PL
946 } else {
947 return allZerosIP;
948 }
949 } catch (const std::exception &e) {
950 return allZerosIP;
951 }
952 }
953 ret.resize(ret.size() - 1); // remove trailing dot after last octet
954 return ret;
061c3e9f
PD
955 }
956 if(input.length() >= 8) {
957 auto last8 = input.substr(input.length()-8);
958 if(sscanf(last8.c_str(), "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
5fb49130 959 return std::to_string(x1) + "." + std::to_string(x2) + "." + std::to_string(x3) + "." + std::to_string(x4);
061c3e9f 960 }
c9d40b1f 961 }
c9d40b1f 962 }
448e7a2d 963 return allZerosIP;
c9d40b1f 964 });
965
c13af350
CHB
966 lua.writeFunction("createForward6", []() {
967 DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
c9d40b1f 968 auto parts = rel.getRawLabels();
969 if(parts.size()==8) {
970 string tot;
971 for(int i=0; i<8; ++i) {
972 if(i)
973 tot.append(1,':');
974 tot+=parts[i];
975 }
976 ComboAddress ca(tot);
977 return ca.toString();
978 }
979 else if(parts.size()==1) {
ada6063a
PD
980 if (parts[0].find('-') != std::string::npos) {
981 boost::replace_all(parts[0],"-",":");
982 ComboAddress ca(parts[0]);
983 return ca.toString();
984 } else {
985 if (parts[0].size() >= 32) {
986 auto ippart = parts[0].substr(parts[0].size()-32);
987 auto fulladdress =
988 ippart.substr(0, 4) + ":" +
989 ippart.substr(4, 4) + ":" +
990 ippart.substr(8, 4) + ":" +
991 ippart.substr(12, 4) + ":" +
992 ippart.substr(16, 4) + ":" +
993 ippart.substr(20, 4) + ":" +
994 ippart.substr(24, 4) + ":" +
995 ippart.substr(28, 4);
996
997 ComboAddress ca(fulladdress);
998 return ca.toString();
999 }
1000 }
c9d40b1f 1001 }
1002
1003 return std::string("::");
1004 });
54c20d39 1005 lua.writeFunction("createReverse6", [](string format, boost::optional<std::unordered_map<string,string>> e){
c9d40b1f 1006 vector<ComboAddress> candidates;
1007
1008 try {
c13af350 1009 auto labels= s_lua_record_ctx->qname.getRawLabels();
c9d40b1f 1010 if(labels.size()<32)
1011 return std::string("unknown");
54c20d39 1012 boost::format fmt(format);
c9d40b1f 1013 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
1bc56192 1014
c9d40b1f 1015
1016 string together;
1017 vector<string> quads;
1018 for(int i=0; i<8; ++i) {
1019 if(i)
1020 together+=":";
a3739f30 1021 string lquad;
c9d40b1f 1022 for(int j=0; j <4; ++j) {
a3739f30 1023 lquad.append(1, labels[31-i*4-j][0]);
c9d40b1f 1024 together += labels[31-i*4-j][0];
1025 }
a3739f30 1026 quads.push_back(lquad);
c9d40b1f 1027 }
1028 ComboAddress ip6(together,0);
1029
1030 if(e) {
1031 auto& addrs=*e;
1032 for(const auto& addr: addrs) {
1033 // this makes sure we catch all forms of the address
1034 if(ComboAddress(addr.first,0)==ip6)
1035 return addr.second;
1036 }
1037 }
1bc56192 1038
c9d40b1f 1039 string dashed=ip6.toString();
1040 boost::replace_all(dashed, ":", "-");
1bc56192 1041
c9d40b1f 1042 for(int i=31; i>=0; --i)
1043 fmt % labels[i];
1044 fmt % dashed;
1045
a3739f30
OM
1046 for(const auto& lquad : quads)
1047 fmt % lquad;
1bc56192 1048
c9d40b1f 1049 return fmt.str();
1050 }
af6bf47c 1051 catch(std::exception& ex) {
e7929442 1052 g_log<<Logger::Error<<"LUA Record exception: "<<ex.what()<<endl;
c9d40b1f 1053 }
af6bf47c
OM
1054 catch(PDNSException& ex) {
1055 g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl;
c9d40b1f 1056 }
1057 return std::string("unknown");
1058 });
1059
863fa7f6 1060 lua.writeFunction("filterForward", [](const string& address, NetmaskGroup& nmg, boost::optional<string> fallback) -> vector<string> {
223bfcad
PD
1061 ComboAddress ca(address);
1062
1063 if (nmg.match(ComboAddress(address))) {
863fa7f6 1064 return {address};
223bfcad
PD
1065 } else {
1066 if (fallback) {
98301eb0
PD
1067 if (fallback->empty()) {
1068 // if fallback is an empty string, return an empty array
863fa7f6 1069 return {};
98301eb0 1070 }
863fa7f6 1071 return {*fallback};
223bfcad
PD
1072 }
1073
1074 if (ca.isIPv4()) {
863fa7f6 1075 return {string("0.0.0.0")};
223bfcad 1076 } else {
863fa7f6 1077 return {string("::")};
223bfcad
PD
1078 }
1079 }
1080 });
1081
1bc56192
CHB
1082 /*
1083 * Simplistic test to see if an IP address listens on a certain port
1084 * Will return a single IP address from the set of available IP addresses. If
1085 * no IP address is available, will return a random element of the set of
ef2ea4bf 1086 * addresses supplied for testing.
1bc56192
CHB
1087 *
1088 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
1089 */
6c8714d2 1090 lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, const boost::optional<std::unordered_map<string,string>> options) {
6c8714d2
CHB
1091 if (port < 0) {
1092 port = 0;
1093 }
1094 if (port > std::numeric_limits<uint16_t>::max()) {
1095 port = std::numeric_limits<uint16_t>::max();
1096 }
6c8714d2 1097
04c53783
CHB
1098 auto checker = [](const ComboAddress& addr, const opts_t& opts) {
1099 return g_up.isUp(addr, opts);
1100 };
224085cc 1101 return genericIfUp(ips, options, checker, port);
1bc56192 1102 });
b7edebf8 1103
95d0df69
PD
1104 lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) {
1105 vector<ComboAddress> candidates;
1106 opts_t opts;
1107 if(options)
1108 opts = *options;
1109
1110 ComboAddress ca_unspec;
1111 ca_unspec.sin4.sin_family=AF_UNSPEC;
1112
1113 // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1114 for (const auto& [count, unitmap] : ipurls) {
1115 // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1116 vector<ComboAddress> available;
1117
1118 for (const auto& [ipStr, url] : unitmap) {
1119 // unit: ["192.0.2.1"] = "https://example.com"
1120 ComboAddress ip(ipStr);
1121 candidates.push_back(ip);
1122 if (g_up.isUp(ca_unspec, url, opts)) {
1123 available.push_back(ip);
1124 }
1125 }
1126 if(!available.empty()) {
1127 vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
60f9d9a2 1128 return convComboAddressListToString(res);
95d0df69
PD
1129 }
1130 }
1131
1132 // All units down, apply backupSelector on all candidates
1133 vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
60f9d9a2 1134 return convComboAddressListToString(res);
95d0df69
PD
1135 });
1136
c13af350 1137 lua.writeFunction("ifurlup", [](const std::string& url,
1bc56192
CHB
1138 const boost::variant<iplist_t, ipunitlist_t>& ips,
1139 boost::optional<opts_t> options) {
b7edebf8 1140
04c53783
CHB
1141 auto checker = [&url](const ComboAddress& addr, const opts_t& opts) {
1142 return g_up.isUp(addr, url, opts);
1143 };
1144 return genericIfUp(ips, options, checker);
2fe7bbf8 1145 });
1bc56192
CHB
1146 /*
1147 * Returns a random IP address from the supplied list
1148 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
1149 */
1150 lua.writeFunction("pickrandom", [](const iplist_t& ips) {
60f9d9a2 1151 vector<string> items = convStringList(ips);
f52e47eb 1152 return pickRandom<string>(items);
b7edebf8 1153 });
1154
60f9d9a2 1155 lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
1156 vector<string> items = convStringList(ips);
f52e47eb 1157 return pickRandomSample<string>(n, items);
60f9d9a2 1158 });
b7edebf8 1159
60f9d9a2 1160 lua.writeFunction("pickhashed", [](const iplist_t& ips) {
1161 vector<string> items = convStringList(ips);
f52e47eb 1162 return pickHashed<string>(s_lua_record_ctx->bestwho, items);
60f9d9a2 1163 });
1bc56192
CHB
1164 /*
1165 * Returns a random IP address from the supplied list, as weighted by the
1166 * various ``weight`` parameters
1167 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1168 */
1169 lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
60f9d9a2 1170 vector< pair<int, string> > items = convIntStringPairList(ips);
f52e47eb 1171 return pickWeightedRandom<string>(items);
975f0839 1172 });
1173
1bc56192
CHB
1174 /*
1175 * Based on the hash of `bestwho`, returns an IP address from the list
1176 * supplied, as weighted by the various `weight` parameters
1177 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1178 */
c13af350 1179 lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) {
60f9d9a2 1180 vector< pair<int, string> > items;
1bc56192 1181
60f9d9a2 1182 items.reserve(ips.size());
b4608224
CHB
1183 for (auto& entry : ips) {
1184 items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]);
4a0d414b 1185 }
1bc56192 1186
f52e47eb 1187 return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
975f0839 1188 });
1189
d33b8bf4
BR
1190 /*
1191 * Based on the hash of the record name, return an IP address from the list
1192 * supplied, as weighted by the various `weight` parameters
1193 * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1194 */
1195 lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t > ips) {
1196 vector< pair<int, string> > items;
1197
1198 items.reserve(ips.size());
1199 for(auto& i : ips)
dc03f001 1200 {
d33b8bf4 1201 items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
dc03f001 1202 }
d33b8bf4
BR
1203
1204 return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items);
1205 });
4a0d414b
CHB
1206 /*
1207 * Based on the hash of `bestwho`, returns an IP address from the list
1208 * supplied, as weighted by the various `weight` parameters and distributed consistently
1209 * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1210 */
a6be268f
CHB
1211 lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) {
1212 std::vector<std::pair<int, std::string>> items;
4a0d414b
CHB
1213
1214 items.reserve(ips.size());
a6be268f
CHB
1215 for (const auto& entry : ips) {
1216 items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2));
4a0d414b
CHB
1217 }
1218
1219 return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items);
1220 });
975f0839 1221
c13af350 1222 lua.writeFunction("pickclosest", [](const iplist_t& ips) {
60f9d9a2 1223 vector<ComboAddress> conv = convComboAddressList(ips);
1bc56192 1224
c13af350 1225 return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
1bc56192 1226
b7edebf8 1227 });
1228
c13af350
CHB
1229 if (g_luaRecordExecLimit > 0) {
1230 lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
1231 }
1bc56192 1232
d73de874 1233 lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){
b7edebf8 1234 throw std::runtime_error("Script took too long");
1235 });
b7edebf8 1236
f9423419 1237 lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
1d56dac6
PD
1238 return getGeo(ip, attr);
1239 });
1240
b7edebf8 1241 typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
60f9d9a2 1242
1243 lua.writeFunction("asnum", [](const combovar_t& asns) {
1244 string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
1245 return doCompare(asns, res, [](const std::string& a, const std::string& b) {
1246 return !strcasecmp(a.c_str(), b.c_str());
1247 });
1248 });
c13af350
CHB
1249 lua.writeFunction("continent", [](const combovar_t& continent) {
1250 string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
b7edebf8 1251 return doCompare(continent, res, [](const std::string& a, const std::string& b) {
1252 return !strcasecmp(a.c_str(), b.c_str());
1253 });
1254 });
60f9d9a2 1255 lua.writeFunction("continentCode", []() {
1256 string unknown("unknown");
1257 string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
1258 if ( res == unknown ) {
1259 return std::string("--");
1260 }
1261 return res;
1262 });
1263 lua.writeFunction("country", [](const combovar_t& var) {
1264 string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
1265 return doCompare(var, res, [](const std::string& a, const std::string& b) {
b7edebf8 1266 return !strcasecmp(a.c_str(), b.c_str());
1267 });
60f9d9a2 1268
b7edebf8 1269 });
60f9d9a2 1270 lua.writeFunction("countryCode", []() {
1271 string unknown("unknown");
c13af350 1272 string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
60f9d9a2 1273 if ( res == unknown ) {
1274 return std::string("--");
1275 }
1276 return res;
1277 });
1278 lua.writeFunction("region", [](const combovar_t& var) {
1279 string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
b7edebf8 1280 return doCompare(var, res, [](const std::string& a, const std::string& b) {
1281 return !strcasecmp(a.c_str(), b.c_str());
1282 });
1bc56192 1283
b7edebf8 1284 });
60f9d9a2 1285 lua.writeFunction("regionCode", []() {
1286 string unknown("unknown");
1287 string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
1288 if ( res == unknown ) {
1289 return std::string("--");
1290 }
1291 return res;
1292 });
c13af350 1293 lua.writeFunction("netmask", [](const iplist_t& ips) {
b7edebf8 1294 for(const auto& i :ips) {
1295 Netmask nm(i.second);
c13af350 1296 if(nm.match(s_lua_record_ctx->bestwho))
b7edebf8 1297 return true;
1298 }
1299 return false;
1300 });
b7edebf8 1301 /* {
1302 {
1bc56192 1303 {'192.168.0.0/16', '10.0.0.0/8'},
b7edebf8 1304 {'192.168.20.20', '192.168.20.21'}
1305 },
1306 {
1307 {'0.0.0.0/0'}, {'192.0.2.1'}
1308 }
1309 }
1bc56192 1310 */
c13af350 1311 lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) {
b7edebf8 1312 for(const auto& rule : in) {
1313 const auto& netmasks=rule.second[0].second;
1314 const auto& destinations=rule.second[1].second;
1315 for(const auto& nmpair : netmasks) {
1316 Netmask nm(nmpair.second);
c13af350 1317 if(nm.match(s_lua_record_ctx->bestwho)) {
28beeb0f
PD
1318 if (destinations.empty()) {
1319 throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm.toString() + ")");
1320 }
b51ef4f9 1321 return destinations[dns_random(destinations.size())].second;
b7edebf8 1322 }
1323 }
1324 }
1325 return std::string();
60f9d9a2 1326 });
1bc56192 1327
60f9d9a2 1328 lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
1329 vector<string> result;
1330 result.reserve(ips.size());
bb85386d 1331
60f9d9a2 1332 for(const auto& ip : ips) {
1333 result.emplace_back(ip.second);
1334 }
1335 if(result.empty()) {
1336 throw std::invalid_argument("The IP list cannot be empty");
1337 }
1338 return result;
1339 });
1bc56192 1340
2ef08930 1341 lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) {
3bf2f94d 1342 DNSName rec;
3bf2f94d
PD
1343 vector<string> ret;
1344 try {
1345 rec = DNSName(record);
3bf2f94d
PD
1346 }
1347 catch (const std::exception& e) {
2ef08930 1348 g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl;
3bf2f94d
PD
1349 return ret;
1350 }
1351 try {
1352 SOAData soaData;
1353
1354 if (!getAuth(rec, qtype, &soaData)) {
1355 return ret;
1356 }
1357
1358 vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id);
1359 for (const auto& drec : drs) {
1360 ret.push_back(drec.dr.getContent()->getZoneRepresentation());
1361 }
1362 }
1363 catch (std::exception& e) {
33826bd1 1364 g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl;
3bf2f94d
PD
1365 }
1366 return ret;
1367 });
1368
c13af350 1369 lua.writeFunction("include", [&lua](string record) {
388d3a43 1370 DNSName rec;
b7edebf8 1371 try {
388d3a43
PL
1372 rec = DNSName(record) + s_lua_record_ctx->zone;
1373 } catch (const std::exception &e){
51a4e451 1374 g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl;
388d3a43
PL
1375 return;
1376 }
1377 try {
1378 vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zoneid);
c9d40b1f 1379 for(const auto& dr : drs) {
b7edebf8 1380 auto lr = getRR<LUARecordContent>(dr.dr);
1381 lua.executeCode(lr->getCode());
1382 }
25bcfaec 1383 }
1384 catch(std::exception& e) {
388d3a43 1385 g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<rec<<": "<<e.what()<<endl;
25bcfaec 1386 }
b7edebf8 1387 });
c13af350
CHB
1388}
1389
87f1cd7c 1390std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype, unique_ptr<AuthLua4>& LUA)
c13af350 1391{
87f1cd7c 1392 if(!LUA || // we don't have a Lua state yet
c13af350 1393 !g_LuaRecordSharedState) { // or we want a new one even if we had one
87f1cd7c
PD
1394 LUA = make_unique<AuthLua4>();
1395 setupLuaRecords(*LUA->getLua());
c13af350
CHB
1396 }
1397
1398 std::vector<shared_ptr<DNSRecordContent>> ret;
1399
87f1cd7c 1400 LuaContext& lua = *LUA->getLua();
c13af350 1401
2bbc9eb0 1402 s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
c13af350
CHB
1403 s_lua_record_ctx->qname = query;
1404 s_lua_record_ctx->zone = zone;
1405 s_lua_record_ctx->zoneid = zoneid;
bb85386d 1406
c13af350
CHB
1407 lua.writeVariable("qname", query);
1408 lua.writeVariable("zone", zone);
1409 lua.writeVariable("zoneid", zoneid);
4172a5b2 1410 lua.writeVariable("who", dnsp.getInnerRemote());
514df45b 1411 lua.writeVariable("localwho", dnsp.getLocal());
c13af350
CHB
1412 lua.writeVariable("dh", (dnsheader*)&dnsp.d);
1413 lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
1414 lua.writeVariable("tcp", dnsp.d_tcp);
1415 lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
1416 if(dnsp.hasEDNSSubnet()) {
1417 lua.writeVariable("ecswho", dnsp.getRealRemote());
1418 s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
1419 }
1420 else {
1421 lua.writeVariable("ecswho", nullptr);
4172a5b2 1422 s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
c13af350
CHB
1423 }
1424 lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
b7edebf8 1425
b7edebf8 1426 try {
1427 string actual;
1428 if(!code.empty() && code[0]!=';')
6180eab4
PD
1429 actual = "return " + code;
1430 else
1431 actual = code.substr(1);
c9d40b1f 1432
b7edebf8 1433 auto content=lua.executeCode<boost::variant<string, vector<pair<int, string> > > >(actual);
c9d40b1f 1434
b7edebf8 1435 vector<string> contents;
1436 if(auto str = boost::get<string>(&content))
1437 contents.push_back(*str);
1438 else
1439 for(const auto& c : boost::get<vector<pair<int,string>>>(content))
1440 contents.push_back(c.second);
1bc56192 1441
690b86b7 1442 for(const auto& content_it: contents) {
b7edebf8 1443 if(qtype==QType::TXT)
d525b58b 1444 ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
b7edebf8 1445 else
d525b58b 1446 ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
b7edebf8 1447 }
1bc56192 1448 } catch(std::exception &e) {
d5fcd583 1449 g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
3217f4ea
PD
1450 try {
1451 std::rethrow_if_nested(e);
1452 g_log<<endl;
1453 } catch(const std::exception& ne) {
1454 g_log << ": " << ne.what() << std::endl;
1455 }
1456 catch(const PDNSException& ne) {
1457 g_log << ": " << ne.reason << std::endl;
1458 }
1bc56192 1459 throw ;
b7edebf8 1460 }
1461
1462 return ret;
1463}