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