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