]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/lua-record.cc
Fix warnings about dup/missing prototypes
[thirdparty/pdns.git] / pdns / lua-record.cc
1 #include <future>
2 #include <mutex>
3 #include <boost/format.hpp>
4 #include "version.hh"
5 #include "ext/luawrapper/include/LuaContext.hpp"
6 #include "lua-auth4.hh"
7 #include "sstuff.hh"
8 #include "minicurl.hh"
9 #include "ueberbackend.hh"
10 #include "dnsrecords.hh"
11 #include "dns_random.hh"
12 #include "common_startup.hh"
13 #include "../modules/geoipbackend/geoipinterface.hh" // only for the enum
14
15 /* to do:
16 block AXFR unless TSIG, or override
17
18 investigate IPv6
19
20 check the wildcard 'no cache' stuff, we may get it wrong
21
22 ponder ECS scopemask setting
23
24 ponder netmask tree from file for huge number of netmasks
25
26 unify ifupurl/ifupport
27 add attribute for certificate check
28 add list of current monitors
29 expire them too?
30
31 pool of UeberBackends?
32
33 Pool checks ?
34 */
35
36 extern int g_luaRecordExecLimit;
37
38 using iplist_t = vector<pair<int, string> >;
39 using wiplist_t = std::unordered_map<int, string>;
40 using ipunitlist_t = vector<pair<int, iplist_t> >;
41 using opts_t = std::unordered_map<string,string>;
42
43 class IsUpOracle
44 {
45 private:
46 struct CheckDesc
47 {
48 ComboAddress rem;
49 string url;
50 opts_t opts;
51 bool operator<(const CheckDesc& rhs) const
52 {
53 std::map<string,string> oopts, rhsoopts;
54 for(const auto& m : opts)
55 oopts[m.first]=m.second;
56 for(const auto& m : rhs.opts)
57 rhsoopts[m.first]=m.second;
58
59 return std::make_tuple(rem, url, oopts) <
60 std::make_tuple(rhs.rem, rhs.url, rhsoopts);
61 }
62 };
63 struct CheckState
64 {
65 CheckState(time_t _lastAccess): lastAccess(_lastAccess) {}
66 /* current status */
67 std::atomic<bool> status{false};
68 /* first check ? */
69 std::atomic<bool> first{true};
70 /* last time the status was accessed */
71 std::atomic<time_t> lastAccess{0};
72 };
73
74 pthread_rwlock_t d_lock;
75 public:
76 IsUpOracle()
77 {
78 pthread_rwlock_init(&d_lock, nullptr);
79 }
80 ~IsUpOracle()
81 {
82 pthread_rwlock_destroy(&d_lock);
83 }
84 bool isUp(const ComboAddress& remote, const opts_t& opts);
85 bool isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts);
86 bool isUp(const CheckDesc& cd);
87
88 private:
89 void checkURL(const CheckDesc& cd, const bool status, const bool first = false)
90 {
91 try {
92 int timeout = 1;
93 if (cd.opts.count("timeout")) {
94 timeout = std::atoi(cd.opts.at("timeout").c_str());
95 }
96 string useragent = productName();
97 if (cd.opts.count("useragent")) {
98 useragent = cd.opts.at("useragent");
99 }
100 MiniCurl mc(useragent);
101
102 string content;
103 if (cd.opts.count("source")) {
104 ComboAddress src(cd.opts.at("source"));
105 content=mc.getURL(cd.url, &cd.rem, &src, timeout);
106 }
107 else {
108 content=mc.getURL(cd.url, &cd.rem, nullptr, timeout);
109 }
110 if (cd.opts.count("stringmatch") && content.find(cd.opts.at("stringmatch")) == string::npos) {
111 throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % cd.opts.at("stringmatch")));
112 }
113 if(!status) {
114 g_log<<Logger::Info<<"LUA record monitoring declaring "<<cd.rem.toString()<<" UP for URL "<<cd.url<<"!"<<endl;
115 }
116 setUp(cd);
117 }
118 catch(std::exception& ne) {
119 if(status || first)
120 g_log<<Logger::Info<<"LUA record monitoring declaring "<<cd.rem.toString()<<" DOWN for URL "<<cd.url<<", error: "<<ne.what()<<endl;
121 setDown(cd);
122 }
123 }
124 void checkTCP(const CheckDesc& cd, const bool status, const bool first = false) {
125 try {
126 int timeout = 1;
127 if (cd.opts.count("timeout")) {
128 timeout = std::atoi(cd.opts.at("timeout").c_str());
129 }
130 Socket s(cd.rem.sin4.sin_family, SOCK_STREAM);
131 ComboAddress src;
132 s.setNonBlocking();
133 if (cd.opts.count("source")) {
134 src = ComboAddress(cd.opts.at("source"));
135 s.bind(src);
136 }
137 s.connect(cd.rem, timeout);
138 if (!status) {
139 g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" ";
140 if(cd.opts.count("source"))
141 g_log<<"(source "<<src.toString()<<") ";
142 g_log<<"UP!"<<endl;
143 }
144 setUp(cd);
145 }
146 catch (const NetworkError& ne) {
147 if(status || first) {
148 g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" DOWN: "<<ne.what()<<endl;
149 }
150 setDown(cd);
151 }
152 }
153 void checkThread()
154 {
155 while (true)
156 {
157 std::chrono::system_clock::time_point checkStart = std::chrono::system_clock::now();
158 std::vector<std::future<void>> results;
159 std::vector<CheckDesc> toDelete;
160 {
161 ReadLock lock{&d_lock}; // make sure there's no insertion
162 for (auto& it: d_statuses) {
163 auto& desc = it.first;
164 auto& state = it.second;
165
166 if (desc.url.empty()) { // TCP
167 results.push_back(std::async(std::launch::async, &IsUpOracle::checkTCP, this, desc, state->status.load(), state->first.load()));
168 } else { // URL
169 results.push_back(std::async(std::launch::async, &IsUpOracle::checkURL, this, desc, state->status.load(), state->first.load()));
170 }
171 if (std::chrono::system_clock::from_time_t(state->lastAccess) < (checkStart - std::chrono::seconds(g_luaHealthChecksExpireDelay))) {
172 toDelete.push_back(desc);
173 }
174 }
175 }
176 // we can release the lock as nothing will be deleted
177 for (auto& future: results) {
178 future.wait();
179 }
180 if (!toDelete.empty()) {
181 WriteLock lock{&d_lock};
182 for (auto& it: toDelete) {
183 d_statuses.erase(it);
184 }
185 }
186 std::this_thread::sleep_until(checkStart + std::chrono::seconds(g_luaHealthChecksInterval));
187 }
188 }
189
190 typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
191 statuses_t d_statuses;
192
193 std::unique_ptr<std::thread> d_checkerThread;
194
195 void setStatus(const CheckDesc& cd, bool status)
196 {
197 ReadLock lock{&d_lock};
198 auto& state = d_statuses[cd];
199 state->status = status;
200 if (state->first) {
201 state->first = false;
202 }
203 }
204
205 void setDown(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
206 {
207 CheckDesc cd{rem, url, opts};
208 setStatus(cd, false);
209 }
210
211 void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
212 {
213 CheckDesc cd{rem, url, opts};
214
215 setStatus(cd, true);
216 }
217
218 void setDown(const CheckDesc& cd)
219 {
220 setStatus(cd, false);
221 }
222
223 void setUp(const CheckDesc& cd)
224 {
225 setStatus(cd, true);
226 }
227 };
228
229 bool IsUpOracle::isUp(const CheckDesc& cd)
230 {
231 if (!d_checkerThread) {
232 d_checkerThread = std::unique_ptr<std::thread>(new std::thread(&IsUpOracle::checkThread, this));
233 }
234 time_t now = time(nullptr);
235 {
236 ReadLock lock{&d_lock};
237 auto iter = d_statuses.find(cd);
238 if (iter != d_statuses.end()) {
239 iter->second->lastAccess = now;
240 return iter->second->status;
241 }
242 }
243 // try to parse options so we don't insert any malformed content
244 if (cd.opts.count("source")) {
245 ComboAddress src(cd.opts.at("source"));
246 }
247 {
248 WriteLock lock{&d_lock};
249 // Make sure we don't insert new entry twice now we have the lock
250 if (d_statuses.find(cd) == d_statuses.end()) {
251 d_statuses[cd] = std::unique_ptr<CheckState>(new CheckState{now});
252 }
253 }
254 return false;
255 }
256
257 bool IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts)
258 {
259 CheckDesc cd{remote, "", opts};
260 return isUp(cd);
261 }
262
263 bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts)
264 {
265 CheckDesc cd{remote, url, opts};
266 return isUp(cd);
267 }
268
269 IsUpOracle g_up;
270 namespace {
271 template<typename T, typename C>
272 bool doCompare(const T& var, const std::string& res, const C& cmp)
273 {
274 if(auto country = boost::get<string>(&var))
275 return cmp(*country, res);
276
277 auto countries=boost::get<vector<pair<int,string> > >(&var);
278 for(const auto& country : *countries) {
279 if(cmp(country.second, res))
280 return true;
281 }
282 return false;
283 }
284 }
285
286
287 static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
288 {
289 static bool initialized;
290 extern std::function<std::string(const std::string& ip, int)> g_getGeo;
291 if(!g_getGeo) {
292 if(!initialized) {
293 g_log<<Logger::Error<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl;
294 initialized=true;
295 }
296 return "unknown";
297 }
298 else
299 return g_getGeo(ip, (int)qa);
300 }
301
302 static ComboAddress pickrandom(const vector<ComboAddress>& ips)
303 {
304 if (ips.empty()) {
305 throw std::invalid_argument("The IP list cannot be empty");
306 }
307 return ips[dns_random(ips.size())];
308 }
309
310 static ComboAddress hashed(const ComboAddress& who, const vector<ComboAddress>& ips)
311 {
312 if (ips.empty()) {
313 throw std::invalid_argument("The IP list cannot be empty");
314 }
315 ComboAddress::addressOnlyHash aoh;
316 return ips[aoh(who) % ips.size()];
317 }
318
319
320 static ComboAddress pickwrandom(const vector<pair<int,ComboAddress> >& wips)
321 {
322 if (wips.empty()) {
323 throw std::invalid_argument("The IP list cannot be empty");
324 }
325 int sum=0;
326 vector<pair<int, ComboAddress> > pick;
327 for(auto& i : wips) {
328 sum += i.first;
329 pick.push_back({sum, i.second});
330 }
331 int r = dns_random(sum);
332 auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const decltype(pick)::value_type& a) { return rarg < a.first; });
333 return p->second;
334 }
335
336 static ComboAddress pickwhashed(const ComboAddress& bestwho, vector<pair<int,ComboAddress> >& wips)
337 {
338 if (wips.empty()) {
339 return ComboAddress();
340 }
341 int sum=0;
342 vector<pair<int, ComboAddress> > pick;
343 for(auto& i : wips) {
344 sum += i.first;
345 pick.push_back({sum, i.second});
346 }
347 ComboAddress::addressOnlyHash aoh;
348 int r = aoh(bestwho) % sum;
349 auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const decltype(pick)::value_type& a) { return rarg < a.first; });
350 return p->second;
351 }
352
353 static bool getLatLon(const std::string& ip, double& lat, double& lon)
354 {
355 string inp = getGeo(ip, GeoIPInterface::Location);
356 if(inp.empty())
357 return false;
358 lat=atof(inp.c_str());
359 auto pos=inp.find(' ');
360 if(pos != string::npos)
361 lon=atof(inp.c_str() + pos);
362 return true;
363 }
364
365 static bool getLatLon(const std::string& ip, string& loc)
366 {
367 int latdeg, latmin, londeg, lonmin;
368 double latsec, lonsec;
369 char lathem='X', lonhem='X';
370
371 double lat, lon;
372 if(!getLatLon(ip, lat, lon))
373 return false;
374
375 if(lat > 0) {
376 lathem='N';
377 }
378 else {
379 lat = -lat;
380 lathem='S';
381 }
382
383 if(lon > 0) {
384 lonhem='E';
385 }
386 else {
387 lon = -lon;
388 lonhem='W';
389 }
390
391 /*
392 >>> deg = int(R)
393 >>> min = int((R - int(R)) * 60.0)
394 >>> sec = (((R - int(R)) * 60.0) - min) * 60.0
395 >>> print("{}º {}' {}\"".format(deg, min, sec))
396 */
397
398
399 latdeg = lat;
400 latmin = (lat - latdeg)*60.0;
401 latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
402
403 londeg = lon;
404 lonmin = (lon - londeg)*60.0;
405 lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
406
407 // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
408
409 boost::format fmt("%d %d %d %c %d %d %d %c 0.00m 1.00m 10000.00m 10.00m");
410
411 loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
412 return true;
413 }
414
415 static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips)
416 {
417 if (wips.empty()) {
418 throw std::invalid_argument("The IP list cannot be empty");
419 }
420 map<double,vector<ComboAddress> > ranked;
421 double wlat=0, wlon=0;
422 getLatLon(bestwho.toString(), wlat, wlon);
423 // cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
424 vector<string> ret;
425 for(const auto& c : wips) {
426 double lat=0, lon=0;
427 getLatLon(c.toString(), lat, lon);
428 // cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
429 double latdiff = wlat-lat;
430 double londiff = wlon-lon;
431 if(londiff > 180)
432 londiff = 360 - londiff;
433 double dist2=latdiff*latdiff + londiff*londiff;
434 // cout<<" distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
435 ranked[dist2].push_back(c);
436 }
437 return ranked.begin()->second[dns_random(ranked.begin()->second.size())];
438 }
439
440 static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid)
441 {
442 static UeberBackend ub;
443 static std::mutex mut;
444 std::lock_guard<std::mutex> lock(mut);
445 ub.lookup(QType(qtype), name, zoneid);
446 DNSZoneRecord dr;
447 vector<DNSZoneRecord> ret;
448 while(ub.get(dr)) {
449 ret.push_back(dr);
450 }
451 return ret;
452 }
453
454 static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
455 {
456 string selector=defaultValue;
457 if(options) {
458 if(options->count(name))
459 selector=options->find(name)->second;
460 }
461 return selector;
462 }
463
464 static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
465 {
466 vector<ComboAddress> ret;
467
468 if(selector=="all")
469 return candidates;
470 else if(selector=="random")
471 ret.emplace_back(pickrandom(candidates));
472 else if(selector=="pickclosest")
473 ret.emplace_back(pickclosest(bestwho, candidates));
474 else if(selector=="hashed")
475 ret.emplace_back(hashed(bestwho, candidates));
476 else {
477 g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl;
478 ret.emplace_back(pickrandom(candidates));
479 }
480
481 return ret;
482 }
483
484 static vector<string> convIpListToString(const vector<ComboAddress> &comboAddresses)
485 {
486 vector<string> ret;
487
488 for (const auto& c : comboAddresses) {
489 ret.emplace_back(c.toString());
490 }
491
492 return ret;
493 }
494
495 static vector<ComboAddress> convIplist(const iplist_t& src)
496 {
497 vector<ComboAddress> ret;
498
499 for(const auto& ip : src) {
500 ret.emplace_back(ip.second);
501 }
502
503 return ret;
504 }
505
506 static vector<pair<int, ComboAddress> > convWIplist(std::unordered_map<int, wiplist_t > src)
507 {
508 vector<pair<int,ComboAddress> > ret;
509
510 for(const auto& i : src) {
511 ret.emplace_back(atoi(i.second.at(1).c_str()), ComboAddress(i.second.at(2)));
512 }
513
514 return ret;
515 }
516
517 static thread_local unique_ptr<AuthLua4> s_LUA;
518 bool g_LuaRecordSharedState;
519
520 typedef struct AuthLuaRecordContext
521 {
522 ComboAddress bestwho;
523 DNSName qname;
524 DNSName zone;
525 int zoneid;
526 } lua_record_ctx_t;
527
528 static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
529
530 static void setupLuaRecords()
531 {
532 LuaContext& lua = *s_LUA->getLua();
533
534 lua.writeFunction("latlon", []() {
535 double lat, lon;
536 getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
537 return std::to_string(lat)+" "+std::to_string(lon);
538 });
539 lua.writeFunction("latlonloc", []() {
540 string loc;
541 getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
542 return loc;
543 });
544 lua.writeFunction("closestMagic", []() {
545 vector<ComboAddress> candidates;
546 // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
547 for(auto l : s_lua_record_ctx->qname.getRawLabels()) {
548 boost::replace_all(l, "-", ".");
549 try {
550 candidates.emplace_back(l);
551 } catch (const PDNSException& e) {
552 // no need to continue as we most likely reached the end of the ip list
553 break ;
554 }
555 }
556 return pickclosest(s_lua_record_ctx->bestwho, candidates).toString();
557 });
558 lua.writeFunction("latlonMagic", [](){
559 auto labels= s_lua_record_ctx->qname.getRawLabels();
560 if(labels.size()<4)
561 return std::string("unknown");
562 double lat, lon;
563 getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
564 return std::to_string(lat)+" "+std::to_string(lon);
565 });
566
567
568 lua.writeFunction("createReverse", [](string suffix, boost::optional<std::unordered_map<string,string>> e){
569 try {
570 auto labels = s_lua_record_ctx->qname.getRawLabels();
571 if(labels.size()<4)
572 return std::string("unknown");
573
574 vector<ComboAddress> candidates;
575
576 // exceptions are relative to zone
577 // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
578 // e["1.2.3.4"]="bert.powerdns.com" - should match, easy enough to do
579 // the issue is with classless delegation..
580 if(e) {
581 ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
582 const auto& uom = *e;
583 for(const auto& c : uom)
584 if(ComboAddress(c.first, 0) == req)
585 return c.second;
586 }
587 boost::format fmt(suffix);
588 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
589 fmt % labels[3] % labels[2] % labels[1] % labels[0];
590
591 fmt % (labels[3]+"-"+labels[2]+"-"+labels[1]+"-"+labels[0]);
592
593 boost::format fmt2("%02x%02x%02x%02x");
594 for(int i=3; i>=0; --i)
595 fmt2 % atoi(labels[i].c_str());
596
597 fmt % (fmt2.str());
598
599 return fmt.str();
600 }
601 catch(std::exception& ex) {
602 g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
603 }
604 return std::string("error");
605 });
606 lua.writeFunction("createForward", []() {
607 DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
608 auto parts = rel.getRawLabels();
609 if(parts.size()==4)
610 return parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3];
611 if(parts.size()==1) {
612 // either hex string, or 12-13-14-15
613 // cout<<parts[0]<<endl;
614 unsigned int x1, x2, x3, x4;
615 if(sscanf(parts[0].c_str()+2, "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
616 return std::to_string(x1)+"."+std::to_string(x2)+"."+std::to_string(x3)+"."+std::to_string(x4);
617 }
618
619
620 }
621 return std::string("0.0.0.0");
622 });
623
624 lua.writeFunction("createForward6", []() {
625 DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
626 auto parts = rel.getRawLabels();
627 if(parts.size()==8) {
628 string tot;
629 for(int i=0; i<8; ++i) {
630 if(i)
631 tot.append(1,':');
632 tot+=parts[i];
633 }
634 ComboAddress ca(tot);
635 return ca.toString();
636 }
637 else if(parts.size()==1) {
638 boost::replace_all(parts[0],"-",":");
639 ComboAddress ca(parts[0]);
640 return ca.toString();
641 }
642
643 return std::string("::");
644 });
645 lua.writeFunction("createReverse6", [](string suffix, boost::optional<std::unordered_map<string,string>> e){
646 vector<ComboAddress> candidates;
647
648 try {
649 auto labels= s_lua_record_ctx->qname.getRawLabels();
650 if(labels.size()<32)
651 return std::string("unknown");
652 boost::format fmt(suffix);
653 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
654
655
656 string together;
657 vector<string> quads;
658 for(int i=0; i<8; ++i) {
659 if(i)
660 together+=":";
661 string quad;
662 for(int j=0; j <4; ++j) {
663 quad.append(1, labels[31-i*4-j][0]);
664 together += labels[31-i*4-j][0];
665 }
666 quads.push_back(quad);
667 }
668 ComboAddress ip6(together,0);
669
670 if(e) {
671 auto& addrs=*e;
672 for(const auto& addr: addrs) {
673 // this makes sure we catch all forms of the address
674 if(ComboAddress(addr.first,0)==ip6)
675 return addr.second;
676 }
677 }
678
679 string dashed=ip6.toString();
680 boost::replace_all(dashed, ":", "-");
681
682 for(int i=31; i>=0; --i)
683 fmt % labels[i];
684 fmt % dashed;
685
686 for(const auto& quad : quads)
687 fmt % quad;
688
689 return fmt.str();
690 }
691 catch(std::exception& ex) {
692 g_log<<Logger::Error<<"LUA Record xception: "<<ex.what()<<endl;
693 }
694 catch(PDNSException& ex) {
695 g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl;
696 }
697 return std::string("unknown");
698 });
699
700 /*
701 * Simplistic test to see if an IP address listens on a certain port
702 * Will return a single IP address from the set of available IP addresses. If
703 * no IP address is available, will return a random element of the set of
704 * addresses supplied for testing.
705 *
706 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
707 */
708 lua.writeFunction("ifportup", [](int port, const vector<pair<int, string> >& ips, const boost::optional<std::unordered_map<string,string>> options) {
709 vector<ComboAddress> candidates, unavailables;
710 opts_t opts;
711 vector<ComboAddress > conv;
712 std::string selector;
713
714 if(options)
715 opts = *options;
716 for(const auto& i : ips) {
717 ComboAddress rem(i.second, port);
718 if(g_up.isUp(rem, opts)) {
719 candidates.push_back(rem);
720 }
721 else {
722 unavailables.push_back(rem);
723 }
724 }
725 if(!candidates.empty()) {
726 // use regular selector
727 selector = getOptionValue(options, "selector", "random");
728 } else {
729 // All units are down, apply backupSelector on all candidates
730 candidates = std::move(unavailables);
731 selector = getOptionValue(options, "backupSelector", "random");
732 }
733
734 vector<ComboAddress> res = useSelector(selector, s_lua_record_ctx->bestwho, candidates);
735 return convIpListToString(res);
736 });
737
738 lua.writeFunction("ifurlup", [](const std::string& url,
739 const boost::variant<iplist_t, ipunitlist_t>& ips,
740 boost::optional<opts_t> options) {
741 vector<vector<ComboAddress> > candidates;
742 opts_t opts;
743 if(options)
744 opts = *options;
745 if(auto simple = boost::get<iplist_t>(&ips)) {
746 vector<ComboAddress> unit = convIplist(*simple);
747 candidates.push_back(unit);
748 } else {
749 auto units = boost::get<ipunitlist_t>(ips);
750 for(const auto& u : units) {
751 vector<ComboAddress> unit = convIplist(u.second);
752 candidates.push_back(unit);
753 }
754 }
755
756 for(const auto& unit : candidates) {
757 vector<ComboAddress> available;
758 for(const auto& c : unit) {
759 if(g_up.isUp(c, url, opts)) {
760 available.push_back(c);
761 }
762 }
763 if(!available.empty()) {
764 vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
765 return convIpListToString(res);
766 }
767 }
768
769 // All units down, apply backupSelector on all candidates
770 vector<ComboAddress> ret{};
771 for(const auto& unit : candidates) {
772 ret.insert(ret.end(), unit.begin(), unit.end());
773 }
774
775 vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
776 return convIpListToString(res);
777 });
778 /*
779 * Returns a random IP address from the supplied list
780 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
781 */
782 lua.writeFunction("pickrandom", [](const iplist_t& ips) {
783 vector<ComboAddress> conv = convIplist(ips);
784
785 return pickrandom(conv).toString();
786 });
787
788
789 /*
790 * Returns a random IP address from the supplied list, as weighted by the
791 * various ``weight`` parameters
792 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
793 */
794 lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
795 vector<pair<int,ComboAddress> > conv = convWIplist(ips);
796
797 return pickwrandom(conv).toString();
798 });
799
800 /*
801 * Based on the hash of `bestwho`, returns an IP address from the list
802 * supplied, as weighted by the various `weight` parameters
803 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
804 */
805 lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) {
806 vector<pair<int,ComboAddress> > conv;
807
808 for(auto& i : ips)
809 conv.emplace_back(atoi(i.second[1].c_str()), ComboAddress(i.second[2]));
810
811 return pickwhashed(s_lua_record_ctx->bestwho, conv).toString();
812 });
813
814
815 lua.writeFunction("pickclosest", [](const iplist_t& ips) {
816 vector<ComboAddress > conv = convIplist(ips);
817
818 return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
819
820 });
821
822 if (g_luaRecordExecLimit > 0) {
823 lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
824 }
825
826 lua.writeFunction("report", [](string event, boost::optional<string> line){
827 throw std::runtime_error("Script took too long");
828 });
829
830 lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
831 return getGeo(ip, attr);
832 });
833
834 typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
835 lua.writeFunction("continent", [](const combovar_t& continent) {
836 string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
837 return doCompare(continent, res, [](const std::string& a, const std::string& b) {
838 return !strcasecmp(a.c_str(), b.c_str());
839 });
840 });
841 lua.writeFunction("asnum", [](const combovar_t& asns) {
842 string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
843 return doCompare(asns, res, [](const std::string& a, const std::string& b) {
844 return !strcasecmp(a.c_str(), b.c_str());
845 });
846 });
847 lua.writeFunction("country", [](const combovar_t& var) {
848 string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
849 return doCompare(var, res, [](const std::string& a, const std::string& b) {
850 return !strcasecmp(a.c_str(), b.c_str());
851 });
852
853 });
854 lua.writeFunction("netmask", [](const iplist_t& ips) {
855 for(const auto& i :ips) {
856 Netmask nm(i.second);
857 if(nm.match(s_lua_record_ctx->bestwho))
858 return true;
859 }
860 return false;
861 });
862 /* {
863 {
864 {'192.168.0.0/16', '10.0.0.0/8'},
865 {'192.168.20.20', '192.168.20.21'}
866 },
867 {
868 {'0.0.0.0/0'}, {'192.0.2.1'}
869 }
870 }
871 */
872 lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) {
873 for(const auto& rule : in) {
874 const auto& netmasks=rule.second[0].second;
875 const auto& destinations=rule.second[1].second;
876 for(const auto& nmpair : netmasks) {
877 Netmask nm(nmpair.second);
878 if(nm.match(s_lua_record_ctx->bestwho)) {
879 if (destinations.empty()) {
880 throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm.toString() + ")");
881 }
882 return destinations[dns_random(destinations.size())].second;
883 }
884 }
885 }
886 return std::string();
887 }
888 );
889
890
891 lua.writeFunction("include", [&lua](string record) {
892 try {
893 vector<DNSZoneRecord> drs = lookup(DNSName(record) + s_lua_record_ctx->zone, QType::LUA, s_lua_record_ctx->zoneid);
894 for(const auto& dr : drs) {
895 auto lr = getRR<LUARecordContent>(dr.dr);
896 lua.executeCode(lr->getCode());
897 }
898 }
899 catch(std::exception& e) {
900 g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<(DNSName(record)+s_lua_record_ctx->zone)<<": "<<e.what()<<endl;
901 }
902 });
903 }
904
905 std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype)
906 {
907 if(!s_LUA || // we don't have a Lua state yet
908 !g_LuaRecordSharedState) { // or we want a new one even if we had one
909 s_LUA = make_unique<AuthLua4>();
910 setupLuaRecords();
911 }
912
913 std::vector<shared_ptr<DNSRecordContent>> ret;
914
915 LuaContext& lua = *s_LUA->getLua();
916
917 s_lua_record_ctx = std::unique_ptr<lua_record_ctx_t>(new lua_record_ctx_t());
918 s_lua_record_ctx->qname = query;
919 s_lua_record_ctx->zone = zone;
920 s_lua_record_ctx->zoneid = zoneid;
921
922 lua.writeVariable("qname", query);
923 lua.writeVariable("zone", zone);
924 lua.writeVariable("zoneid", zoneid);
925 lua.writeVariable("who", dnsp.getRemote());
926 lua.writeVariable("dh", (dnsheader*)&dnsp.d);
927 lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
928 lua.writeVariable("tcp", dnsp.d_tcp);
929 lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
930 if(dnsp.hasEDNSSubnet()) {
931 lua.writeVariable("ecswho", dnsp.getRealRemote());
932 s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
933 }
934 else {
935 lua.writeVariable("ecswho", nullptr);
936 s_lua_record_ctx->bestwho = dnsp.getRemote();
937 }
938 lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
939
940 try {
941 string actual;
942 if(!code.empty() && code[0]!=';')
943 actual = "return " + code;
944 else
945 actual = code.substr(1);
946
947 auto content=lua.executeCode<boost::variant<string, vector<pair<int, string> > > >(actual);
948
949 vector<string> contents;
950 if(auto str = boost::get<string>(&content))
951 contents.push_back(*str);
952 else
953 for(const auto& c : boost::get<vector<pair<int,string>>>(content))
954 contents.push_back(c.second);
955
956 for(const auto& content_it: contents) {
957 if(qtype==QType::TXT)
958 ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, '"'+content_it+'"' ));
959 else
960 ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, content_it ));
961 }
962 } catch(std::exception &e) {
963 g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).getName()<<") reported: " << e.what();
964 try {
965 std::rethrow_if_nested(e);
966 g_log<<endl;
967 } catch(const std::exception& ne) {
968 g_log << ": " << ne.what() << std::endl;
969 }
970 catch(const PDNSException& ne) {
971 g_log << ": " << ne.reason << std::endl;
972 }
973 throw ;
974 }
975
976 return ret;
977 }