]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/lua-record.cc
Merge pull request #9056 from omoerbeek/decl-warnings
[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 if (sum == 0) {
348 /* we should not have any weight of zero, but better safe than sorry */
349 return ComboAddress();
350 }
351 ComboAddress::addressOnlyHash aoh;
352 int r = aoh(bestwho) % sum;
353 auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const decltype(pick)::value_type& a) { return rarg < a.first; });
354 return p->second;
355 }
356
357 static bool getLatLon(const std::string& ip, double& lat, double& lon)
358 {
359 string inp = getGeo(ip, GeoIPInterface::Location);
360 if(inp.empty())
361 return false;
362 lat=atof(inp.c_str());
363 auto pos=inp.find(' ');
364 if(pos != string::npos)
365 lon=atof(inp.c_str() + pos);
366 return true;
367 }
368
369 static bool getLatLon(const std::string& ip, string& loc)
370 {
371 int latdeg, latmin, londeg, lonmin;
372 double latsec, lonsec;
373 char lathem='X', lonhem='X';
374
375 double lat = 0, lon = 0;
376 if(!getLatLon(ip, lat, lon))
377 return false;
378
379 if(lat > 0) {
380 lathem='N';
381 }
382 else {
383 lat = -lat;
384 lathem='S';
385 }
386
387 if(lon > 0) {
388 lonhem='E';
389 }
390 else {
391 lon = -lon;
392 lonhem='W';
393 }
394
395 /*
396 >>> deg = int(R)
397 >>> min = int((R - int(R)) * 60.0)
398 >>> sec = (((R - int(R)) * 60.0) - min) * 60.0
399 >>> print("{}º {}' {}\"".format(deg, min, sec))
400 */
401
402
403 latdeg = lat;
404 latmin = (lat - latdeg)*60.0;
405 latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
406
407 londeg = lon;
408 lonmin = (lon - londeg)*60.0;
409 lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
410
411 // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
412
413 boost::format fmt("%d %d %d %c %d %d %d %c 0.00m 1.00m 10000.00m 10.00m");
414
415 loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
416 return true;
417 }
418
419 static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips)
420 {
421 if (wips.empty()) {
422 throw std::invalid_argument("The IP list cannot be empty");
423 }
424 map<double,vector<ComboAddress> > ranked;
425 double wlat=0, wlon=0;
426 getLatLon(bestwho.toString(), wlat, wlon);
427 // cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
428 vector<string> ret;
429 for(const auto& c : wips) {
430 double lat=0, lon=0;
431 getLatLon(c.toString(), lat, lon);
432 // cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
433 double latdiff = wlat-lat;
434 double londiff = wlon-lon;
435 if(londiff > 180)
436 londiff = 360 - londiff;
437 double dist2=latdiff*latdiff + londiff*londiff;
438 // cout<<" distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
439 ranked[dist2].push_back(c);
440 }
441 return ranked.begin()->second[dns_random(ranked.begin()->second.size())];
442 }
443
444 static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid)
445 {
446 static UeberBackend ub;
447 static std::mutex mut;
448 std::lock_guard<std::mutex> lock(mut);
449 ub.lookup(QType(qtype), name, zoneid);
450 DNSZoneRecord dr;
451 vector<DNSZoneRecord> ret;
452 while(ub.get(dr)) {
453 ret.push_back(dr);
454 }
455 return ret;
456 }
457
458 static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
459 {
460 string selector=defaultValue;
461 if(options) {
462 if(options->count(name))
463 selector=options->find(name)->second;
464 }
465 return selector;
466 }
467
468 static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
469 {
470 vector<ComboAddress> ret;
471
472 if(selector=="all")
473 return candidates;
474 else if(selector=="random")
475 ret.emplace_back(pickrandom(candidates));
476 else if(selector=="pickclosest")
477 ret.emplace_back(pickclosest(bestwho, candidates));
478 else if(selector=="hashed")
479 ret.emplace_back(hashed(bestwho, candidates));
480 else {
481 g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl;
482 ret.emplace_back(pickrandom(candidates));
483 }
484
485 return ret;
486 }
487
488 static vector<string> convIpListToString(const vector<ComboAddress> &comboAddresses)
489 {
490 vector<string> ret;
491
492 for (const auto& c : comboAddresses) {
493 ret.emplace_back(c.toString());
494 }
495
496 return ret;
497 }
498
499 static vector<ComboAddress> convIplist(const iplist_t& src)
500 {
501 vector<ComboAddress> ret;
502
503 for(const auto& ip : src) {
504 ret.emplace_back(ip.second);
505 }
506
507 return ret;
508 }
509
510 static vector<pair<int, ComboAddress> > convWIplist(std::unordered_map<int, wiplist_t > src)
511 {
512 vector<pair<int,ComboAddress> > ret;
513
514 for(const auto& i : src) {
515 ret.emplace_back(atoi(i.second.at(1).c_str()), ComboAddress(i.second.at(2)));
516 }
517
518 return ret;
519 }
520
521 static thread_local unique_ptr<AuthLua4> s_LUA;
522 bool g_LuaRecordSharedState;
523
524 typedef struct AuthLuaRecordContext
525 {
526 ComboAddress bestwho;
527 DNSName qname;
528 DNSName zone;
529 int zoneid;
530 } lua_record_ctx_t;
531
532 static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
533
534 static void setupLuaRecords()
535 {
536 LuaContext& lua = *s_LUA->getLua();
537
538 lua.writeFunction("latlon", []() {
539 double lat = 0, lon = 0;
540 getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
541 return std::to_string(lat)+" "+std::to_string(lon);
542 });
543 lua.writeFunction("latlonloc", []() {
544 string loc;
545 getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
546 return loc;
547 });
548 lua.writeFunction("closestMagic", []() {
549 vector<ComboAddress> candidates;
550 // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
551 for(auto l : s_lua_record_ctx->qname.getRawLabels()) {
552 boost::replace_all(l, "-", ".");
553 try {
554 candidates.emplace_back(l);
555 } catch (const PDNSException& e) {
556 // no need to continue as we most likely reached the end of the ip list
557 break ;
558 }
559 }
560 return pickclosest(s_lua_record_ctx->bestwho, candidates).toString();
561 });
562 lua.writeFunction("latlonMagic", [](){
563 auto labels= s_lua_record_ctx->qname.getRawLabels();
564 if(labels.size()<4)
565 return std::string("unknown");
566 double lat = 0, lon = 0;
567 getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
568 return std::to_string(lat)+" "+std::to_string(lon);
569 });
570
571
572 lua.writeFunction("createReverse", [](string suffix, boost::optional<std::unordered_map<string,string>> e){
573 try {
574 auto labels = s_lua_record_ctx->qname.getRawLabels();
575 if(labels.size()<4)
576 return std::string("unknown");
577
578 vector<ComboAddress> candidates;
579
580 // exceptions are relative to zone
581 // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
582 // e["1.2.3.4"]="bert.powerdns.com" - should match, easy enough to do
583 // the issue is with classless delegation..
584 if(e) {
585 ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
586 const auto& uom = *e;
587 for(const auto& c : uom)
588 if(ComboAddress(c.first, 0) == req)
589 return c.second;
590 }
591 boost::format fmt(suffix);
592 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
593 fmt % labels[3] % labels[2] % labels[1] % labels[0];
594
595 fmt % (labels[3]+"-"+labels[2]+"-"+labels[1]+"-"+labels[0]);
596
597 boost::format fmt2("%02x%02x%02x%02x");
598 for(int i=3; i>=0; --i)
599 fmt2 % atoi(labels[i].c_str());
600
601 fmt % (fmt2.str());
602
603 return fmt.str();
604 }
605 catch(std::exception& ex) {
606 g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
607 }
608 return std::string("error");
609 });
610 lua.writeFunction("createForward", []() {
611 DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
612 auto parts = rel.getRawLabels();
613 if(parts.size()==4)
614 return parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3];
615 if(parts.size()==1) {
616 // either hex string, or 12-13-14-15
617 // cout<<parts[0]<<endl;
618 unsigned int x1, x2, x3, x4;
619 if(sscanf(parts[0].c_str()+2, "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
620 return std::to_string(x1)+"."+std::to_string(x2)+"."+std::to_string(x3)+"."+std::to_string(x4);
621 }
622
623
624 }
625 return std::string("0.0.0.0");
626 });
627
628 lua.writeFunction("createForward6", []() {
629 DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
630 auto parts = rel.getRawLabels();
631 if(parts.size()==8) {
632 string tot;
633 for(int i=0; i<8; ++i) {
634 if(i)
635 tot.append(1,':');
636 tot+=parts[i];
637 }
638 ComboAddress ca(tot);
639 return ca.toString();
640 }
641 else if(parts.size()==1) {
642 boost::replace_all(parts[0],"-",":");
643 ComboAddress ca(parts[0]);
644 return ca.toString();
645 }
646
647 return std::string("::");
648 });
649 lua.writeFunction("createReverse6", [](string suffix, boost::optional<std::unordered_map<string,string>> e){
650 vector<ComboAddress> candidates;
651
652 try {
653 auto labels= s_lua_record_ctx->qname.getRawLabels();
654 if(labels.size()<32)
655 return std::string("unknown");
656 boost::format fmt(suffix);
657 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
658
659
660 string together;
661 vector<string> quads;
662 for(int i=0; i<8; ++i) {
663 if(i)
664 together+=":";
665 string quad;
666 for(int j=0; j <4; ++j) {
667 quad.append(1, labels[31-i*4-j][0]);
668 together += labels[31-i*4-j][0];
669 }
670 quads.push_back(quad);
671 }
672 ComboAddress ip6(together,0);
673
674 if(e) {
675 auto& addrs=*e;
676 for(const auto& addr: addrs) {
677 // this makes sure we catch all forms of the address
678 if(ComboAddress(addr.first,0)==ip6)
679 return addr.second;
680 }
681 }
682
683 string dashed=ip6.toString();
684 boost::replace_all(dashed, ":", "-");
685
686 for(int i=31; i>=0; --i)
687 fmt % labels[i];
688 fmt % dashed;
689
690 for(const auto& quad : quads)
691 fmt % quad;
692
693 return fmt.str();
694 }
695 catch(std::exception& ex) {
696 g_log<<Logger::Error<<"LUA Record xception: "<<ex.what()<<endl;
697 }
698 catch(PDNSException& ex) {
699 g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl;
700 }
701 return std::string("unknown");
702 });
703
704 /*
705 * Simplistic test to see if an IP address listens on a certain port
706 * Will return a single IP address from the set of available IP addresses. If
707 * no IP address is available, will return a random element of the set of
708 * addresses supplied for testing.
709 *
710 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
711 */
712 lua.writeFunction("ifportup", [](int port, const vector<pair<int, string> >& ips, const boost::optional<std::unordered_map<string,string>> options) {
713 vector<ComboAddress> candidates, unavailables;
714 opts_t opts;
715 vector<ComboAddress > conv;
716 std::string selector;
717
718 if(options)
719 opts = *options;
720 for(const auto& i : ips) {
721 ComboAddress rem(i.second, port);
722 if(g_up.isUp(rem, opts)) {
723 candidates.push_back(rem);
724 }
725 else {
726 unavailables.push_back(rem);
727 }
728 }
729 if(!candidates.empty()) {
730 // use regular selector
731 selector = getOptionValue(options, "selector", "random");
732 } else {
733 // All units are down, apply backupSelector on all candidates
734 candidates = std::move(unavailables);
735 selector = getOptionValue(options, "backupSelector", "random");
736 }
737
738 vector<ComboAddress> res = useSelector(selector, s_lua_record_ctx->bestwho, candidates);
739 return convIpListToString(res);
740 });
741
742 lua.writeFunction("ifurlup", [](const std::string& url,
743 const boost::variant<iplist_t, ipunitlist_t>& ips,
744 boost::optional<opts_t> options) {
745 vector<vector<ComboAddress> > candidates;
746 opts_t opts;
747 if(options)
748 opts = *options;
749 if(auto simple = boost::get<iplist_t>(&ips)) {
750 vector<ComboAddress> unit = convIplist(*simple);
751 candidates.push_back(unit);
752 } else {
753 auto units = boost::get<ipunitlist_t>(ips);
754 for(const auto& u : units) {
755 vector<ComboAddress> unit = convIplist(u.second);
756 candidates.push_back(unit);
757 }
758 }
759
760 for(const auto& unit : candidates) {
761 vector<ComboAddress> available;
762 for(const auto& c : unit) {
763 if(g_up.isUp(c, url, opts)) {
764 available.push_back(c);
765 }
766 }
767 if(!available.empty()) {
768 vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
769 return convIpListToString(res);
770 }
771 }
772
773 // All units down, apply backupSelector on all candidates
774 vector<ComboAddress> ret{};
775 for(const auto& unit : candidates) {
776 ret.insert(ret.end(), unit.begin(), unit.end());
777 }
778
779 vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
780 return convIpListToString(res);
781 });
782 /*
783 * Returns a random IP address from the supplied list
784 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
785 */
786 lua.writeFunction("pickrandom", [](const iplist_t& ips) {
787 vector<ComboAddress> conv = convIplist(ips);
788
789 return pickrandom(conv).toString();
790 });
791
792
793 /*
794 * Returns a random IP address from the supplied list, as weighted by the
795 * various ``weight`` parameters
796 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
797 */
798 lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
799 vector<pair<int,ComboAddress> > conv = convWIplist(ips);
800
801 return pickwrandom(conv).toString();
802 });
803
804 /*
805 * Based on the hash of `bestwho`, returns an IP address from the list
806 * supplied, as weighted by the various `weight` parameters
807 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
808 */
809 lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) {
810 vector<pair<int,ComboAddress> > conv;
811
812 for(auto& i : ips)
813 conv.emplace_back(atoi(i.second[1].c_str()), ComboAddress(i.second[2]));
814
815 return pickwhashed(s_lua_record_ctx->bestwho, conv).toString();
816 });
817
818
819 lua.writeFunction("pickclosest", [](const iplist_t& ips) {
820 vector<ComboAddress > conv = convIplist(ips);
821
822 return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
823
824 });
825
826 if (g_luaRecordExecLimit > 0) {
827 lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
828 }
829
830 lua.writeFunction("report", [](string event, boost::optional<string> line){
831 throw std::runtime_error("Script took too long");
832 });
833
834 lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
835 return getGeo(ip, attr);
836 });
837
838 typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
839 lua.writeFunction("continent", [](const combovar_t& continent) {
840 string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
841 return doCompare(continent, res, [](const std::string& a, const std::string& b) {
842 return !strcasecmp(a.c_str(), b.c_str());
843 });
844 });
845 lua.writeFunction("asnum", [](const combovar_t& asns) {
846 string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
847 return doCompare(asns, res, [](const std::string& a, const std::string& b) {
848 return !strcasecmp(a.c_str(), b.c_str());
849 });
850 });
851 lua.writeFunction("country", [](const combovar_t& var) {
852 string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
853 return doCompare(var, res, [](const std::string& a, const std::string& b) {
854 return !strcasecmp(a.c_str(), b.c_str());
855 });
856
857 });
858 lua.writeFunction("netmask", [](const iplist_t& ips) {
859 for(const auto& i :ips) {
860 Netmask nm(i.second);
861 if(nm.match(s_lua_record_ctx->bestwho))
862 return true;
863 }
864 return false;
865 });
866 /* {
867 {
868 {'192.168.0.0/16', '10.0.0.0/8'},
869 {'192.168.20.20', '192.168.20.21'}
870 },
871 {
872 {'0.0.0.0/0'}, {'192.0.2.1'}
873 }
874 }
875 */
876 lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) {
877 for(const auto& rule : in) {
878 const auto& netmasks=rule.second[0].second;
879 const auto& destinations=rule.second[1].second;
880 for(const auto& nmpair : netmasks) {
881 Netmask nm(nmpair.second);
882 if(nm.match(s_lua_record_ctx->bestwho)) {
883 if (destinations.empty()) {
884 throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm.toString() + ")");
885 }
886 return destinations[dns_random(destinations.size())].second;
887 }
888 }
889 }
890 return std::string();
891 }
892 );
893
894
895 lua.writeFunction("include", [&lua](string record) {
896 try {
897 vector<DNSZoneRecord> drs = lookup(DNSName(record) + s_lua_record_ctx->zone, QType::LUA, s_lua_record_ctx->zoneid);
898 for(const auto& dr : drs) {
899 auto lr = getRR<LUARecordContent>(dr.dr);
900 lua.executeCode(lr->getCode());
901 }
902 }
903 catch(std::exception& e) {
904 g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<(DNSName(record)+s_lua_record_ctx->zone)<<": "<<e.what()<<endl;
905 }
906 });
907 }
908
909 std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype)
910 {
911 if(!s_LUA || // we don't have a Lua state yet
912 !g_LuaRecordSharedState) { // or we want a new one even if we had one
913 s_LUA = make_unique<AuthLua4>();
914 setupLuaRecords();
915 }
916
917 std::vector<shared_ptr<DNSRecordContent>> ret;
918
919 LuaContext& lua = *s_LUA->getLua();
920
921 s_lua_record_ctx = std::unique_ptr<lua_record_ctx_t>(new lua_record_ctx_t());
922 s_lua_record_ctx->qname = query;
923 s_lua_record_ctx->zone = zone;
924 s_lua_record_ctx->zoneid = zoneid;
925
926 lua.writeVariable("qname", query);
927 lua.writeVariable("zone", zone);
928 lua.writeVariable("zoneid", zoneid);
929 lua.writeVariable("who", dnsp.getRemote());
930 lua.writeVariable("dh", (dnsheader*)&dnsp.d);
931 lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
932 lua.writeVariable("tcp", dnsp.d_tcp);
933 lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
934 if(dnsp.hasEDNSSubnet()) {
935 lua.writeVariable("ecswho", dnsp.getRealRemote());
936 s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
937 }
938 else {
939 lua.writeVariable("ecswho", nullptr);
940 s_lua_record_ctx->bestwho = dnsp.getRemote();
941 }
942 lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
943
944 try {
945 string actual;
946 if(!code.empty() && code[0]!=';')
947 actual = "return " + code;
948 else
949 actual = code.substr(1);
950
951 auto content=lua.executeCode<boost::variant<string, vector<pair<int, string> > > >(actual);
952
953 vector<string> contents;
954 if(auto str = boost::get<string>(&content))
955 contents.push_back(*str);
956 else
957 for(const auto& c : boost::get<vector<pair<int,string>>>(content))
958 contents.push_back(c.second);
959
960 for(const auto& content_it: contents) {
961 if(qtype==QType::TXT)
962 ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, '"'+content_it+'"' ));
963 else
964 ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, content_it ));
965 }
966 } catch(std::exception &e) {
967 g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).getName()<<") reported: " << e.what();
968 try {
969 std::rethrow_if_nested(e);
970 g_log<<endl;
971 } catch(const std::exception& ne) {
972 g_log << ": " << ne.what() << std::endl;
973 }
974 catch(const PDNSException& ne) {
975 g_log << ": " << ne.reason << std::endl;
976 }
977 throw ;
978 }
979
980 return ret;
981 }