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