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