]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/lua-record.cc
rec: Only log qname parsing errors when 'log-common-errors' is set
[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 std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype)
469 {
470 AuthLua4 alua;
471
472 std::vector<shared_ptr<DNSRecordContent>> ret;
473
474 LuaContext& lua = *alua.getLua();
475 lua.writeVariable("qname", query);
476 lua.writeVariable("who", dnsp.getRemote());
477 lua.writeVariable("dh", (dnsheader*)&dnsp.d);
478 lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
479 lua.writeVariable("tcp", dnsp.d_tcp);
480 lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
481 ComboAddress bestwho;
482 if(dnsp.hasEDNSSubnet()) {
483 lua.writeVariable("ecswho", dnsp.getRealRemote());
484 bestwho=dnsp.getRealRemote().getNetwork();
485 }
486 else {
487 bestwho=dnsp.getRemote();
488 }
489
490 lua.writeVariable("bestwho", bestwho);
491
492 lua.writeFunction("latlon", [&bestwho]() {
493 double lat, lon;
494 getLatLon(bestwho.toString(), lat, lon);
495 return std::to_string(lat)+" "+std::to_string(lon);
496 });
497
498 lua.writeFunction("latlonloc", [&bestwho]() {
499 string loc;
500 getLatLon(bestwho.toString(), loc);
501 return loc;
502 });
503
504
505 lua.writeFunction("closestMagic", [&bestwho,&query]() {
506 vector<ComboAddress> candidates;
507 // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
508 for(auto l : query.getRawLabels()) {
509 boost::replace_all(l, "-", ".");
510 try {
511 candidates.emplace_back(l);
512 } catch (const PDNSException& e) {
513 // no need to continue as we most likely reached the end of the ip list
514 break ;
515 }
516 }
517 return pickclosest(bestwho, candidates).toString();
518 });
519
520 lua.writeFunction("latlonMagic", [&query](){
521 auto labels= query.getRawLabels();
522 if(labels.size()<4)
523 return std::string("unknown");
524 double lat, lon;
525 getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
526 return std::to_string(lat)+" "+std::to_string(lon);
527 });
528
529
530 lua.writeFunction("createReverse", [&bestwho,&query,&zone](string suffix, boost::optional<std::unordered_map<string,string>> e){
531 try {
532 auto labels= query.getRawLabels();
533 if(labels.size()<4)
534 return std::string("unknown");
535
536 vector<ComboAddress> candidates;
537
538 // exceptions are relative to zone
539 // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
540 // e["1.2.3.4"]="bert.powerdns.com" - should match, easy enough to do
541 // the issue is with classless delegation..
542 if(e) {
543 ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
544 const auto& uom = *e;
545 for(const auto& c : uom)
546 if(ComboAddress(c.first, 0) == req)
547 return c.second;
548 }
549
550
551 boost::format fmt(suffix);
552 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
553 fmt % labels[3] % labels[2] % labels[1] % labels[0];
554
555 fmt % (labels[3]+"-"+labels[2]+"-"+labels[1]+"-"+labels[0]);
556
557 boost::format fmt2("%02x%02x%02x%02x");
558 for(int i=3; i>=0; --i)
559 fmt2 % atoi(labels[i].c_str());
560
561 fmt % (fmt2.str());
562
563 return fmt.str();
564 }
565 catch(std::exception& e) {
566 g_log<<Logger::Error<<"error: "<<e.what()<<endl;
567 }
568 return std::string("error");
569 });
570
571 lua.writeFunction("createForward", [&zone, &query]() {
572 DNSName rel=query.makeRelative(zone);
573 auto parts = rel.getRawLabels();
574 if(parts.size()==4)
575 return parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3];
576 if(parts.size()==1) {
577 // either hex string, or 12-13-14-15
578 // cout<<parts[0]<<endl;
579 unsigned int x1, x2, x3, x4;
580 if(sscanf(parts[0].c_str()+2, "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
581 return std::to_string(x1)+"."+std::to_string(x2)+"."+std::to_string(x3)+"."+std::to_string(x4);
582 }
583
584
585 }
586 return std::string("0.0.0.0");
587 });
588
589 lua.writeFunction("createForward6", [&query,&zone]() {
590 DNSName rel=query.makeRelative(zone);
591 auto parts = rel.getRawLabels();
592 if(parts.size()==8) {
593 string tot;
594 for(int i=0; i<8; ++i) {
595 if(i)
596 tot.append(1,':');
597 tot+=parts[i];
598 }
599 ComboAddress ca(tot);
600 return ca.toString();
601 }
602 else if(parts.size()==1) {
603 boost::replace_all(parts[0],"-",":");
604 ComboAddress ca(parts[0]);
605 return ca.toString();
606 }
607
608 return std::string("::");
609 });
610
611
612 lua.writeFunction("createReverse6", [&bestwho,&query,&zone](string suffix, boost::optional<std::unordered_map<string,string>> e){
613 vector<ComboAddress> candidates;
614
615 try {
616 auto labels= query.getRawLabels();
617 if(labels.size()<32)
618 return std::string("unknown");
619 boost::format fmt(suffix);
620 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
621
622
623 string together;
624 vector<string> quads;
625 for(int i=0; i<8; ++i) {
626 if(i)
627 together+=":";
628 string quad;
629 for(int j=0; j <4; ++j) {
630 quad.append(1, labels[31-i*4-j][0]);
631 together += labels[31-i*4-j][0];
632 }
633 quads.push_back(quad);
634 }
635 ComboAddress ip6(together,0);
636
637 if(e) {
638 auto& addrs=*e;
639 for(const auto& addr: addrs) {
640 // this makes sure we catch all forms of the address
641 if(ComboAddress(addr.first,0)==ip6)
642 return addr.second;
643 }
644 }
645
646 string dashed=ip6.toString();
647 boost::replace_all(dashed, ":", "-");
648
649 for(int i=31; i>=0; --i)
650 fmt % labels[i];
651 fmt % dashed;
652
653 for(const auto& quad : quads)
654 fmt % quad;
655
656 return fmt.str();
657 }
658 catch(std::exception& e) {
659 g_log<<Logger::Error<<"LUA Record xception: "<<e.what()<<endl;
660 }
661 catch(PDNSException& e) {
662 g_log<<Logger::Error<<"LUA Record exception: "<<e.reason<<endl;
663 }
664 return std::string("unknown");
665 });
666
667
668 /*
669 * Simplistic test to see if an IP address listens on a certain port
670 * Will return a single IP address from the set of available IP addresses. If
671 * no IP address is available, will return a random element of the set of
672 * addresses suppplied for testing.
673 *
674 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
675 */
676 lua.writeFunction("ifportup", [&bestwho](int port, const vector<pair<int, string> >& ips, const boost::optional<std::unordered_map<string,string>> options) {
677 vector<ComboAddress> candidates, unavailables;
678 opts_t opts;
679 vector<ComboAddress > conv;
680 std::string selector;
681
682 if(options)
683 opts = *options;
684 for(const auto& i : ips) {
685 ComboAddress rem(i.second, port);
686 if(g_up.isUp(rem, opts)) {
687 candidates.push_back(rem);
688 }
689 else {
690 unavailables.push_back(rem);
691 }
692 }
693 if(!candidates.empty()) {
694 // use regular selector
695 selector = getOptionValue(options, "selector", "random");
696 } else {
697 // All units are down, apply backupSelector on all candidates
698 candidates = std::move(unavailables);
699 selector = getOptionValue(options, "backupSelector", "random");
700 }
701
702 vector<ComboAddress> res = useSelector(selector, bestwho, candidates);
703 return convIpListToString(res);
704 });
705
706 lua.writeFunction("ifurlup", [&bestwho](const std::string& url,
707 const boost::variant<iplist_t, ipunitlist_t>& ips,
708 boost::optional<opts_t> options) {
709
710 vector<vector<ComboAddress> > candidates;
711 opts_t opts;
712 if(options)
713 opts = *options;
714 if(auto simple = boost::get<iplist_t>(&ips)) {
715 vector<ComboAddress> unit = convIplist(*simple);
716 candidates.push_back(unit);
717 } else {
718 auto units = boost::get<ipunitlist_t>(ips);
719 for(const auto& u : units) {
720 vector<ComboAddress> unit = convIplist(u.second);
721 candidates.push_back(unit);
722 }
723 }
724
725 for(const auto& unit : candidates) {
726 vector<ComboAddress> available;
727 for(const auto& c : unit) {
728 if(g_up.isUp(c, url, opts)) {
729 available.push_back(c);
730 }
731 }
732 if(!available.empty()) {
733 vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), bestwho, available);
734 return convIpListToString(res);
735 }
736 }
737
738 // All units down, apply backupSelector on all candidates
739 vector<ComboAddress> ret{};
740 for(const auto& unit : candidates) {
741 ret.insert(ret.end(), unit.begin(), unit.end());
742 }
743
744 vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), bestwho, ret);
745 return convIpListToString(res);
746 });
747
748
749 /* idea: we have policies on vectors of ComboAddresses, like
750 random, pickwrandom, pickwhashed, pickclosest. In C++ this is ComboAddress in,
751 ComboAddress out. In Lua, vector string in, string out */
752
753 /*
754 * Returns a random IP address from the supplied list
755 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
756 */
757 lua.writeFunction("pickrandom", [](const iplist_t& ips) {
758 vector<ComboAddress> conv = convIplist(ips);
759
760 return pickrandom(conv).toString();
761 });
762
763
764 /*
765 * Returns a random IP address from the supplied list, as weighted by the
766 * various ``weight`` parameters
767 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
768 */
769 lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
770 vector<pair<int,ComboAddress> > conv = convWIplist(ips);
771
772 return pickwrandom(conv).toString();
773 });
774
775 /*
776 * Based on the hash of `bestwho`, returns an IP address from the list
777 * supplied, as weighted by the various `weight` parameters
778 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
779 */
780 lua.writeFunction("pickwhashed", [&bestwho](std::unordered_map<int, wiplist_t > ips) {
781 vector<pair<int,ComboAddress> > conv;
782
783 for(auto& i : ips)
784 conv.emplace_back(atoi(i.second[1].c_str()), ComboAddress(i.second[2]));
785
786 return pickwhashed(bestwho, conv).toString();
787 });
788
789
790 lua.writeFunction("pickclosest", [&bestwho](const iplist_t& ips) {
791 vector<ComboAddress > conv = convIplist(ips);
792
793 return pickclosest(bestwho, conv).toString();
794
795 });
796
797
798 int counter=0;
799 lua.writeFunction("report", [&counter](string event, boost::optional<string> line){
800 throw std::runtime_error("Script took too long");
801 });
802 if (g_luaRecordExecLimit > 0) {
803 lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
804 }
805
806 // TODO: make this better. Accept netmask/CA objects; provide names for the attr constants
807 lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
808 return getGeo(ip, attr);
809 });
810
811 typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
812 lua.writeFunction("continent", [&bestwho](const combovar_t& continent) {
813 string res=getGeo(bestwho.toString(), GeoIPInterface::Continent);
814 return doCompare(continent, res, [](const std::string& a, const std::string& b) {
815 return !strcasecmp(a.c_str(), b.c_str());
816 });
817 });
818
819 lua.writeFunction("asnum", [&bestwho](const combovar_t& asns) {
820 string res=getGeo(bestwho.toString(), GeoIPInterface::ASn);
821 return doCompare(asns, res, [](const std::string& a, const std::string& b) {
822 return !strcasecmp(a.c_str(), b.c_str());
823 });
824 });
825
826 lua.writeFunction("country", [&bestwho](const combovar_t& var) {
827 string res = getGeo(bestwho.toString(), GeoIPInterface::Country2);
828 return doCompare(var, res, [](const std::string& a, const std::string& b) {
829 return !strcasecmp(a.c_str(), b.c_str());
830 });
831
832 });
833
834 lua.writeFunction("netmask", [bestwho](const iplist_t& ips) {
835 for(const auto& i :ips) {
836 Netmask nm(i.second);
837 if(nm.match(bestwho))
838 return true;
839 }
840 return false;
841 });
842
843 /* {
844 {
845 {'192.168.0.0/16', '10.0.0.0/8'},
846 {'192.168.20.20', '192.168.20.21'}
847 },
848 {
849 {'0.0.0.0/0'}, {'192.0.2.1'}
850 }
851 }
852 */
853 lua.writeFunction("view", [bestwho](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) {
854 for(const auto& rule : in) {
855 const auto& netmasks=rule.second[0].second;
856 const auto& destinations=rule.second[1].second;
857 for(const auto& nmpair : netmasks) {
858 Netmask nm(nmpair.second);
859 if(nm.match(bestwho)) {
860 return destinations[dns_random(destinations.size())].second;
861 }
862 }
863 }
864 return std::string();
865 }
866 );
867
868
869 lua.writeFunction("include", [&lua,zone,zoneid](string record) {
870 try {
871 vector<DNSZoneRecord> drs = lookup(DNSName(record) +zone, QType::LUA, zoneid);
872 for(const auto& dr : drs) {
873 auto lr = getRR<LUARecordContent>(dr.dr);
874 lua.executeCode(lr->getCode());
875 }
876 }
877 catch(std::exception& e) {
878 g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<(DNSName(record)+zone)<<": "<<e.what()<<endl;
879 }
880 });
881
882 try {
883 string actual;
884 if(!code.empty() && code[0]!=';')
885 actual = "return " + code;
886 else
887 actual = code.substr(1);
888
889 auto content=lua.executeCode<boost::variant<string, vector<pair<int, string> > > >(actual);
890
891 vector<string> contents;
892 if(auto str = boost::get<string>(&content))
893 contents.push_back(*str);
894 else
895 for(const auto& c : boost::get<vector<pair<int,string>>>(content))
896 contents.push_back(c.second);
897
898 for(const auto& content: contents) {
899 if(qtype==QType::TXT)
900 ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, '"'+content+'"' ));
901 else
902 ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, content ));
903 }
904 } catch(std::exception &e) {
905 g_log<<Logger::Error<<"Lua record reported: "<<e.what();
906 try {
907 std::rethrow_if_nested(e);
908 g_log<<endl;
909 } catch(const std::exception& ne) {
910 g_log << ": " << ne.what() << std::endl;
911 }
912 catch(const PDNSException& ne) {
913 g_log << ": " << ne.reason << std::endl;
914 }
915 throw ;
916 }
917
918 return ret;
919 }