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