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