3 #include <boost/format.hpp>
5 #include "ext/luawrapper/include/LuaContext.hpp"
6 #include "lua-auth4.hh"
9 #include "ueberbackend.hh"
10 #include "dnsrecords.hh"
11 #include "dns_random.hh"
12 #include "common_startup.hh"
13 #include "../modules/geoipbackend/geoipinterface.hh" // only for the enum
16 block AXFR unless TSIG, or override
20 check the wildcard 'no cache' stuff, we may get it wrong
22 ponder ECS scopemask setting
24 ponder netmask tree from file for huge number of netmasks
26 unify ifupurl/ifupport
27 add attribute for certificate check
28 add list of current monitors
31 pool of UeberBackends?
36 extern int g_luaRecordExecLimit
;
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
>;
51 bool operator<(const CheckDesc
& rhs
) const
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
;
59 return std::make_tuple(rem
, url
, oopts
) <
60 std::make_tuple(rhs
.rem
, rhs
.url
, rhsoopts
);
65 CheckState(time_t _lastAccess
): lastAccess(_lastAccess
) {}
67 std::atomic
<bool> status
{false};
69 std::atomic
<bool> first
{true};
70 /* last time the status was accessed */
71 std::atomic
<time_t> lastAccess
{0};
74 pthread_rwlock_t d_lock
;
78 pthread_rwlock_init(&d_lock
, nullptr);
82 pthread_rwlock_destroy(&d_lock
);
84 bool isUp(const ComboAddress
& remote
, const opts_t
& opts
);
85 bool isUp(const ComboAddress
& remote
, const std::string
& url
, const opts_t
& opts
);
86 bool isUp(const CheckDesc
& cd
);
89 void checkURL(const CheckDesc
& cd
, const bool status
, const bool first
= false)
93 if (cd
.opts
.count("timeout")) {
94 timeout
= std::atoi(cd
.opts
.at("timeout").c_str());
96 string useragent
= productName();
97 if (cd
.opts
.count("useragent")) {
98 useragent
= cd
.opts
.at("useragent");
100 MiniCurl
mc(useragent
);
103 if (cd
.opts
.count("source")) {
104 ComboAddress
src(cd
.opts
.at("source"));
105 content
=mc
.getURL(cd
.url
, &cd
.rem
, &src
, timeout
);
108 content
=mc
.getURL(cd
.url
, &cd
.rem
, nullptr, timeout
);
110 if (cd
.opts
.count("stringmatch") && content
.find(cd
.opts
.at("stringmatch")) == string::npos
) {
111 throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % cd
.opts
.at("stringmatch")));
114 g_log
<<Logger::Info
<<"LUA record monitoring declaring "<<cd
.rem
.toString()<<" UP for URL "<<cd
.url
<<"!"<<endl
;
118 catch(std::exception
& ne
) {
120 g_log
<<Logger::Info
<<"LUA record monitoring declaring "<<cd
.rem
.toString()<<" DOWN for URL "<<cd
.url
<<", error: "<<ne
.what()<<endl
;
124 void checkTCP(const CheckDesc
& cd
, const bool status
, const bool first
= false) {
127 if (cd
.opts
.count("timeout")) {
128 timeout
= std::atoi(cd
.opts
.at("timeout").c_str());
130 Socket
s(cd
.rem
.sin4
.sin_family
, SOCK_STREAM
);
133 if (cd
.opts
.count("source")) {
134 src
= ComboAddress(cd
.opts
.at("source"));
137 s
.connect(cd
.rem
, timeout
);
139 g_log
<<Logger::Info
<<"Lua record monitoring declaring TCP/IP "<<cd
.rem
.toStringWithPort()<<" ";
140 if(cd
.opts
.count("source"))
141 g_log
<<"(source "<<src
.toString()<<") ";
146 catch (const NetworkError
& ne
) {
147 if(status
|| first
) {
148 g_log
<<Logger::Info
<<"Lua record monitoring declaring TCP/IP "<<cd
.rem
.toStringWithPort()<<" DOWN: "<<ne
.what()<<endl
;
157 std::chrono::system_clock::time_point checkStart
= std::chrono::system_clock::now();
158 std::vector
<std::future
<void>> results
;
159 std::vector
<CheckDesc
> toDelete
;
161 ReadLock lock
{&d_lock
}; // make sure there's no insertion
162 for (auto& it
: d_statuses
) {
163 auto& desc
= it
.first
;
164 auto& state
= it
.second
;
166 if (desc
.url
.empty()) { // TCP
167 results
.push_back(std::async(std::launch::async
, &IsUpOracle::checkTCP
, this, desc
, state
->status
.load(), state
->first
.load()));
169 results
.push_back(std::async(std::launch::async
, &IsUpOracle::checkURL
, this, desc
, state
->status
.load(), state
->first
.load()));
171 if (std::chrono::system_clock::from_time_t(state
->lastAccess
) < (checkStart
- std::chrono::seconds(g_luaHealthChecksExpireDelay
))) {
172 toDelete
.push_back(desc
);
176 // we can release the lock as nothing will be deleted
177 for (auto& future
: results
) {
180 if (!toDelete
.empty()) {
181 WriteLock lock
{&d_lock
};
182 for (auto& it
: toDelete
) {
183 d_statuses
.erase(it
);
186 std::this_thread::sleep_until(checkStart
+ std::chrono::seconds(g_luaHealthChecksInterval
));
190 typedef map
<CheckDesc
, std::unique_ptr
<CheckState
>> statuses_t
;
191 statuses_t d_statuses
;
193 std::unique_ptr
<std::thread
> d_checkerThread
;
195 void setStatus(const CheckDesc
& cd
, bool status
)
197 ReadLock lock
{&d_lock
};
198 auto& state
= d_statuses
[cd
];
199 state
->status
= status
;
201 state
->first
= false;
205 void setDown(const ComboAddress
& rem
, const std::string
& url
=std::string(), const opts_t
& opts
= opts_t())
207 CheckDesc cd
{rem
, url
, opts
};
208 setStatus(cd
, false);
211 void setUp(const ComboAddress
& rem
, const std::string
& url
=std::string(), const opts_t
& opts
= opts_t())
213 CheckDesc cd
{rem
, url
, opts
};
218 void setDown(const CheckDesc
& cd
)
220 setStatus(cd
, false);
223 void setUp(const CheckDesc
& cd
)
229 bool IsUpOracle::isUp(const CheckDesc
& cd
)
231 if (!d_checkerThread
) {
232 d_checkerThread
= std::unique_ptr
<std::thread
>(new std::thread(&IsUpOracle::checkThread
, this));
234 time_t now
= time(nullptr);
236 ReadLock lock
{&d_lock
};
237 auto iter
= d_statuses
.find(cd
);
238 if (iter
!= d_statuses
.end()) {
239 iter
->second
->lastAccess
= now
;
240 return iter
->second
->status
;
243 // try to parse options so we don't insert any malformed content
244 if (cd
.opts
.count("source")) {
245 ComboAddress
src(cd
.opts
.at("source"));
248 WriteLock lock
{&d_lock
};
249 // Make sure we don't insert new entry twice now we have the lock
250 if (d_statuses
.find(cd
) == d_statuses
.end()) {
251 d_statuses
[cd
] = std::unique_ptr
<CheckState
>(new CheckState
{now
});
257 bool IsUpOracle::isUp(const ComboAddress
& remote
, const opts_t
& opts
)
259 CheckDesc cd
{remote
, "", opts
};
263 bool IsUpOracle::isUp(const ComboAddress
& remote
, const std::string
& url
, const opts_t
& opts
)
265 CheckDesc cd
{remote
, url
, opts
};
271 template<typename T
, typename C
>
272 bool doCompare(const T
& var
, const std::string
& res
, const C
& cmp
)
274 if(auto country
= boost::get
<string
>(&var
))
275 return cmp(*country
, res
);
277 auto countries
=boost::get
<vector
<pair
<int,string
> > >(&var
);
278 for(const auto& country
: *countries
) {
279 if(cmp(country
.second
, res
))
287 static std::string
getGeo(const std::string
& ip
, GeoIPInterface::GeoIPQueryAttribute qa
)
289 static bool initialized
;
290 extern std::function
<std::string(const std::string
& ip
, int)> g_getGeo
;
293 g_log
<<Logger::Error
<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl
;
299 return g_getGeo(ip
, (int)qa
);
302 static ComboAddress
pickrandom(const vector
<ComboAddress
>& ips
)
305 throw std::invalid_argument("The IP list cannot be empty");
307 return ips
[dns_random(ips
.size())];
310 static ComboAddress
hashed(const ComboAddress
& who
, const vector
<ComboAddress
>& ips
)
313 throw std::invalid_argument("The IP list cannot be empty");
315 ComboAddress::addressOnlyHash aoh
;
316 return ips
[aoh(who
) % ips
.size()];
320 static ComboAddress
pickwrandom(const vector
<pair
<int,ComboAddress
> >& wips
)
323 throw std::invalid_argument("The IP list cannot be empty");
326 vector
<pair
<int, ComboAddress
> > pick
;
327 for(auto& i
: wips
) {
329 pick
.push_back({sum
, i
.second
});
331 int r
= dns_random(sum
);
332 auto p
= upper_bound(pick
.begin(), pick
.end(), r
, [](int rarg
, const decltype(pick
)::value_type
& a
) { return rarg
< a
.first
; });
336 static ComboAddress
pickwhashed(const ComboAddress
& bestwho
, vector
<pair
<int,ComboAddress
> >& wips
)
339 return ComboAddress();
342 vector
<pair
<int, ComboAddress
> > pick
;
343 for(auto& i
: wips
) {
345 pick
.push_back({sum
, i
.second
});
348 /* we should not have any weight of zero, but better safe than sorry */
349 return ComboAddress();
351 ComboAddress::addressOnlyHash aoh
;
352 int r
= aoh(bestwho
) % sum
;
353 auto p
= upper_bound(pick
.begin(), pick
.end(), r
, [](int rarg
, const decltype(pick
)::value_type
& a
) { return rarg
< a
.first
; });
357 static bool getLatLon(const std::string
& ip
, double& lat
, double& lon
)
359 string inp
= getGeo(ip
, GeoIPInterface::Location
);
362 lat
=atof(inp
.c_str());
363 auto pos
=inp
.find(' ');
364 if(pos
!= string::npos
)
365 lon
=atof(inp
.c_str() + pos
);
369 static bool getLatLon(const std::string
& ip
, string
& loc
)
371 int latdeg
, latmin
, londeg
, lonmin
;
372 double latsec
, lonsec
;
373 char lathem
='X', lonhem
='X';
375 double lat
= 0, lon
= 0;
376 if(!getLatLon(ip
, lat
, lon
))
397 >>> min = int((R - int(R)) * 60.0)
398 >>> sec = (((R - int(R)) * 60.0) - min) * 60.0
399 >>> print("{}º {}' {}\"".format(deg, min, sec))
404 latmin
= (lat
- latdeg
)*60.0;
405 latsec
= (((lat
- latdeg
)*60.0) - latmin
)*60.0;
408 lonmin
= (lon
- londeg
)*60.0;
409 lonsec
= (((lon
- londeg
)*60.0) - lonmin
)*60.0;
411 // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
413 boost::format
fmt("%d %d %d %c %d %d %d %c 0.00m 1.00m 10000.00m 10.00m");
415 loc
= (fmt
% latdeg
% latmin
% latsec
% lathem
% londeg
% lonmin
% lonsec
% lonhem
).str();
419 static ComboAddress
pickclosest(const ComboAddress
& bestwho
, const vector
<ComboAddress
>& wips
)
422 throw std::invalid_argument("The IP list cannot be empty");
424 map
<double,vector
<ComboAddress
> > ranked
;
425 double wlat
=0, wlon
=0;
426 getLatLon(bestwho
.toString(), wlat
, wlon
);
427 // cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
429 for(const auto& c
: wips
) {
431 getLatLon(c
.toString(), lat
, lon
);
432 // cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
433 double latdiff
= wlat
-lat
;
434 double londiff
= wlon
-lon
;
436 londiff
= 360 - londiff
;
437 double dist2
=latdiff
*latdiff
+ londiff
*londiff
;
438 // cout<<" distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
439 ranked
[dist2
].push_back(c
);
441 return ranked
.begin()->second
[dns_random(ranked
.begin()->second
.size())];
444 static std::vector
<DNSZoneRecord
> lookup(const DNSName
& name
, uint16_t qtype
, int zoneid
)
446 static UeberBackend ub
;
447 static std::mutex mut
;
448 std::lock_guard
<std::mutex
> lock(mut
);
449 ub
.lookup(QType(qtype
), name
, zoneid
);
451 vector
<DNSZoneRecord
> ret
;
458 static std::string
getOptionValue(const boost::optional
<std::unordered_map
<string
, string
>>& options
, const std::string
&name
, const std::string
&defaultValue
)
460 string selector
=defaultValue
;
462 if(options
->count(name
))
463 selector
=options
->find(name
)->second
;
468 static vector
<ComboAddress
> useSelector(const std::string
&selector
, const ComboAddress
& bestwho
, const vector
<ComboAddress
>& candidates
)
470 vector
<ComboAddress
> ret
;
474 else if(selector
=="random")
475 ret
.emplace_back(pickrandom(candidates
));
476 else if(selector
=="pickclosest")
477 ret
.emplace_back(pickclosest(bestwho
, candidates
));
478 else if(selector
=="hashed")
479 ret
.emplace_back(hashed(bestwho
, candidates
));
481 g_log
<<Logger::Warning
<<"LUA Record called with unknown selector '"<<selector
<<"'"<<endl
;
482 ret
.emplace_back(pickrandom(candidates
));
488 static vector
<string
> convIpListToString(const vector
<ComboAddress
> &comboAddresses
)
492 for (const auto& c
: comboAddresses
) {
493 ret
.emplace_back(c
.toString());
499 static vector
<ComboAddress
> convIplist(const iplist_t
& src
)
501 vector
<ComboAddress
> ret
;
503 for(const auto& ip
: src
) {
504 ret
.emplace_back(ip
.second
);
510 static vector
<pair
<int, ComboAddress
> > convWIplist(std::unordered_map
<int, wiplist_t
> src
)
512 vector
<pair
<int,ComboAddress
> > ret
;
514 for(const auto& i
: src
) {
515 ret
.emplace_back(atoi(i
.second
.at(1).c_str()), ComboAddress(i
.second
.at(2)));
521 static thread_local unique_ptr
<AuthLua4
> s_LUA
;
522 bool g_LuaRecordSharedState
;
524 typedef struct AuthLuaRecordContext
526 ComboAddress bestwho
;
532 static thread_local unique_ptr
<lua_record_ctx_t
> s_lua_record_ctx
;
534 static void setupLuaRecords()
536 LuaContext
& lua
= *s_LUA
->getLua();
538 lua
.writeFunction("latlon", []() {
539 double lat
= 0, lon
= 0;
540 getLatLon(s_lua_record_ctx
->bestwho
.toString(), lat
, lon
);
541 return std::to_string(lat
)+" "+std::to_string(lon
);
543 lua
.writeFunction("latlonloc", []() {
545 getLatLon(s_lua_record_ctx
->bestwho
.toString(), loc
);
548 lua
.writeFunction("closestMagic", []() {
549 vector
<ComboAddress
> candidates
;
550 // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
551 for(auto l
: s_lua_record_ctx
->qname
.getRawLabels()) {
552 boost::replace_all(l
, "-", ".");
554 candidates
.emplace_back(l
);
555 } catch (const PDNSException
& e
) {
556 // no need to continue as we most likely reached the end of the ip list
560 return pickclosest(s_lua_record_ctx
->bestwho
, candidates
).toString();
562 lua
.writeFunction("latlonMagic", [](){
563 auto labels
= s_lua_record_ctx
->qname
.getRawLabels();
565 return std::string("unknown");
566 double lat
= 0, lon
= 0;
567 getLatLon(labels
[3]+"."+labels
[2]+"."+labels
[1]+"."+labels
[0], lat
, lon
);
568 return std::to_string(lat
)+" "+std::to_string(lon
);
572 lua
.writeFunction("createReverse", [](string suffix
, boost::optional
<std::unordered_map
<string
,string
>> e
){
574 auto labels
= s_lua_record_ctx
->qname
.getRawLabels();
576 return std::string("unknown");
578 vector
<ComboAddress
> candidates
;
580 // exceptions are relative to zone
581 // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
582 // e["1.2.3.4"]="bert.powerdns.com" - should match, easy enough to do
583 // the issue is with classless delegation..
585 ComboAddress
req(labels
[3]+"."+labels
[2]+"."+labels
[1]+"."+labels
[0], 0);
586 const auto& uom
= *e
;
587 for(const auto& c
: uom
)
588 if(ComboAddress(c
.first
, 0) == req
)
591 boost::format
fmt(suffix
);
592 fmt
.exceptions( boost::io::all_error_bits
^ ( boost::io::too_many_args_bit
| boost::io::too_few_args_bit
) );
593 fmt
% labels
[3] % labels
[2] % labels
[1] % labels
[0];
595 fmt
% (labels
[3]+"-"+labels
[2]+"-"+labels
[1]+"-"+labels
[0]);
597 boost::format
fmt2("%02x%02x%02x%02x");
598 for(int i
=3; i
>=0; --i
)
599 fmt2
% atoi(labels
[i
].c_str());
605 catch(std::exception
& ex
) {
606 g_log
<<Logger::Error
<<"error: "<<ex
.what()<<endl
;
608 return std::string("error");
610 lua
.writeFunction("createForward", []() {
611 DNSName rel
=s_lua_record_ctx
->qname
.makeRelative(s_lua_record_ctx
->zone
);
612 auto parts
= rel
.getRawLabels();
614 return parts
[0]+"."+parts
[1]+"."+parts
[2]+"."+parts
[3];
615 if(parts
.size()==1) {
616 // either hex string, or 12-13-14-15
617 // cout<<parts[0]<<endl;
618 unsigned int x1
, x2
, x3
, x4
;
619 if(sscanf(parts
[0].c_str()+2, "%02x%02x%02x%02x", &x1
, &x2
, &x3
, &x4
)==4) {
620 return std::to_string(x1
)+"."+std::to_string(x2
)+"."+std::to_string(x3
)+"."+std::to_string(x4
);
625 return std::string("0.0.0.0");
628 lua
.writeFunction("createForward6", []() {
629 DNSName rel
=s_lua_record_ctx
->qname
.makeRelative(s_lua_record_ctx
->zone
);
630 auto parts
= rel
.getRawLabels();
631 if(parts
.size()==8) {
633 for(int i
=0; i
<8; ++i
) {
638 ComboAddress
ca(tot
);
639 return ca
.toString();
641 else if(parts
.size()==1) {
642 boost::replace_all(parts
[0],"-",":");
643 ComboAddress
ca(parts
[0]);
644 return ca
.toString();
647 return std::string("::");
649 lua
.writeFunction("createReverse6", [](string suffix
, boost::optional
<std::unordered_map
<string
,string
>> e
){
650 vector
<ComboAddress
> candidates
;
653 auto labels
= s_lua_record_ctx
->qname
.getRawLabels();
655 return std::string("unknown");
656 boost::format
fmt(suffix
);
657 fmt
.exceptions( boost::io::all_error_bits
^ ( boost::io::too_many_args_bit
| boost::io::too_few_args_bit
) );
661 vector
<string
> quads
;
662 for(int i
=0; i
<8; ++i
) {
666 for(int j
=0; j
<4; ++j
) {
667 quad
.append(1, labels
[31-i
*4-j
][0]);
668 together
+= labels
[31-i
*4-j
][0];
670 quads
.push_back(quad
);
672 ComboAddress
ip6(together
,0);
676 for(const auto& addr
: addrs
) {
677 // this makes sure we catch all forms of the address
678 if(ComboAddress(addr
.first
,0)==ip6
)
683 string dashed
=ip6
.toString();
684 boost::replace_all(dashed
, ":", "-");
686 for(int i
=31; i
>=0; --i
)
690 for(const auto& quad
: quads
)
695 catch(std::exception
& ex
) {
696 g_log
<<Logger::Error
<<"LUA Record xception: "<<ex
.what()<<endl
;
698 catch(PDNSException
& ex
) {
699 g_log
<<Logger::Error
<<"LUA Record exception: "<<ex
.reason
<<endl
;
701 return std::string("unknown");
705 * Simplistic test to see if an IP address listens on a certain port
706 * Will return a single IP address from the set of available IP addresses. If
707 * no IP address is available, will return a random element of the set of
708 * addresses supplied for testing.
710 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
712 lua
.writeFunction("ifportup", [](int port
, const vector
<pair
<int, string
> >& ips
, const boost::optional
<std::unordered_map
<string
,string
>> options
) {
713 vector
<ComboAddress
> candidates
, unavailables
;
715 vector
<ComboAddress
> conv
;
716 std::string selector
;
720 for(const auto& i
: ips
) {
721 ComboAddress
rem(i
.second
, port
);
722 if(g_up
.isUp(rem
, opts
)) {
723 candidates
.push_back(rem
);
726 unavailables
.push_back(rem
);
729 if(!candidates
.empty()) {
730 // use regular selector
731 selector
= getOptionValue(options
, "selector", "random");
733 // All units are down, apply backupSelector on all candidates
734 candidates
= std::move(unavailables
);
735 selector
= getOptionValue(options
, "backupSelector", "random");
738 vector
<ComboAddress
> res
= useSelector(selector
, s_lua_record_ctx
->bestwho
, candidates
);
739 return convIpListToString(res
);
742 lua
.writeFunction("ifurlup", [](const std::string
& url
,
743 const boost::variant
<iplist_t
, ipunitlist_t
>& ips
,
744 boost::optional
<opts_t
> options
) {
745 vector
<vector
<ComboAddress
> > candidates
;
749 if(auto simple
= boost::get
<iplist_t
>(&ips
)) {
750 vector
<ComboAddress
> unit
= convIplist(*simple
);
751 candidates
.push_back(unit
);
753 auto units
= boost::get
<ipunitlist_t
>(ips
);
754 for(const auto& u
: units
) {
755 vector
<ComboAddress
> unit
= convIplist(u
.second
);
756 candidates
.push_back(unit
);
760 for(const auto& unit
: candidates
) {
761 vector
<ComboAddress
> available
;
762 for(const auto& c
: unit
) {
763 if(g_up
.isUp(c
, url
, opts
)) {
764 available
.push_back(c
);
767 if(!available
.empty()) {
768 vector
<ComboAddress
> res
= useSelector(getOptionValue(options
, "selector", "random"), s_lua_record_ctx
->bestwho
, available
);
769 return convIpListToString(res
);
773 // All units down, apply backupSelector on all candidates
774 vector
<ComboAddress
> ret
{};
775 for(const auto& unit
: candidates
) {
776 ret
.insert(ret
.end(), unit
.begin(), unit
.end());
779 vector
<ComboAddress
> res
= useSelector(getOptionValue(options
, "backupSelector", "random"), s_lua_record_ctx
->bestwho
, ret
);
780 return convIpListToString(res
);
783 * Returns a random IP address from the supplied list
784 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
786 lua
.writeFunction("pickrandom", [](const iplist_t
& ips
) {
787 vector
<ComboAddress
> conv
= convIplist(ips
);
789 return pickrandom(conv
).toString();
794 * Returns a random IP address from the supplied list, as weighted by the
795 * various ``weight`` parameters
796 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
798 lua
.writeFunction("pickwrandom", [](std::unordered_map
<int, wiplist_t
> ips
) {
799 vector
<pair
<int,ComboAddress
> > conv
= convWIplist(ips
);
801 return pickwrandom(conv
).toString();
805 * Based on the hash of `bestwho`, returns an IP address from the list
806 * supplied, as weighted by the various `weight` parameters
807 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
809 lua
.writeFunction("pickwhashed", [](std::unordered_map
<int, wiplist_t
> ips
) {
810 vector
<pair
<int,ComboAddress
> > conv
;
813 conv
.emplace_back(atoi(i
.second
[1].c_str()), ComboAddress(i
.second
[2]));
815 return pickwhashed(s_lua_record_ctx
->bestwho
, conv
).toString();
819 lua
.writeFunction("pickclosest", [](const iplist_t
& ips
) {
820 vector
<ComboAddress
> conv
= convIplist(ips
);
822 return pickclosest(s_lua_record_ctx
->bestwho
, conv
).toString();
826 if (g_luaRecordExecLimit
> 0) {
827 lua
.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit
));
830 lua
.writeFunction("report", [](string event
, boost::optional
<string
> line
){
831 throw std::runtime_error("Script took too long");
834 lua
.writeFunction("geoiplookup", [](const string
&ip
, const GeoIPInterface::GeoIPQueryAttribute attr
) {
835 return getGeo(ip
, attr
);
838 typedef const boost::variant
<string
,vector
<pair
<int,string
> > > combovar_t
;
839 lua
.writeFunction("continent", [](const combovar_t
& continent
) {
840 string res
=getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Continent
);
841 return doCompare(continent
, res
, [](const std::string
& a
, const std::string
& b
) {
842 return !strcasecmp(a
.c_str(), b
.c_str());
845 lua
.writeFunction("asnum", [](const combovar_t
& asns
) {
846 string res
=getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::ASn
);
847 return doCompare(asns
, res
, [](const std::string
& a
, const std::string
& b
) {
848 return !strcasecmp(a
.c_str(), b
.c_str());
851 lua
.writeFunction("country", [](const combovar_t
& var
) {
852 string res
= getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Country2
);
853 return doCompare(var
, res
, [](const std::string
& a
, const std::string
& b
) {
854 return !strcasecmp(a
.c_str(), b
.c_str());
858 lua
.writeFunction("netmask", [](const iplist_t
& ips
) {
859 for(const auto& i
:ips
) {
860 Netmask
nm(i
.second
);
861 if(nm
.match(s_lua_record_ctx
->bestwho
))
868 {'192.168.0.0/16', '10.0.0.0/8'},
869 {'192.168.20.20', '192.168.20.21'}
872 {'0.0.0.0/0'}, {'192.0.2.1'}
876 lua
.writeFunction("view", [](const vector
<pair
<int, vector
<pair
<int, iplist_t
> > > >& in
) {
877 for(const auto& rule
: in
) {
878 const auto& netmasks
=rule
.second
[0].second
;
879 const auto& destinations
=rule
.second
[1].second
;
880 for(const auto& nmpair
: netmasks
) {
881 Netmask
nm(nmpair
.second
);
882 if(nm
.match(s_lua_record_ctx
->bestwho
)) {
883 if (destinations
.empty()) {
884 throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm
.toString() + ")");
886 return destinations
[dns_random(destinations
.size())].second
;
890 return std::string();
895 lua
.writeFunction("include", [&lua
](string record
) {
897 vector
<DNSZoneRecord
> drs
= lookup(DNSName(record
) + s_lua_record_ctx
->zone
, QType::LUA
, s_lua_record_ctx
->zoneid
);
898 for(const auto& dr
: drs
) {
899 auto lr
= getRR
<LUARecordContent
>(dr
.dr
);
900 lua
.executeCode(lr
->getCode());
903 catch(std::exception
& e
) {
904 g_log
<<Logger::Error
<<"Failed to load include record for LUArecord "<<(DNSName(record
)+s_lua_record_ctx
->zone
)<<": "<<e
.what()<<endl
;
909 std::vector
<shared_ptr
<DNSRecordContent
>> luaSynth(const std::string
& code
, const DNSName
& query
, const DNSName
& zone
, int zoneid
, const DNSPacket
& dnsp
, uint16_t qtype
)
911 if(!s_LUA
|| // we don't have a Lua state yet
912 !g_LuaRecordSharedState
) { // or we want a new one even if we had one
913 s_LUA
= make_unique
<AuthLua4
>();
917 std::vector
<shared_ptr
<DNSRecordContent
>> ret
;
919 LuaContext
& lua
= *s_LUA
->getLua();
921 s_lua_record_ctx
= std::unique_ptr
<lua_record_ctx_t
>(new lua_record_ctx_t());
922 s_lua_record_ctx
->qname
= query
;
923 s_lua_record_ctx
->zone
= zone
;
924 s_lua_record_ctx
->zoneid
= zoneid
;
926 lua
.writeVariable("qname", query
);
927 lua
.writeVariable("zone", zone
);
928 lua
.writeVariable("zoneid", zoneid
);
929 lua
.writeVariable("who", dnsp
.getRemote());
930 lua
.writeVariable("dh", (dnsheader
*)&dnsp
.d
);
931 lua
.writeVariable("dnssecOK", dnsp
.d_dnssecOk
);
932 lua
.writeVariable("tcp", dnsp
.d_tcp
);
933 lua
.writeVariable("ednsPKTSize", dnsp
.d_ednsRawPacketSizeLimit
);
934 if(dnsp
.hasEDNSSubnet()) {
935 lua
.writeVariable("ecswho", dnsp
.getRealRemote());
936 s_lua_record_ctx
->bestwho
= dnsp
.getRealRemote().getNetwork();
939 lua
.writeVariable("ecswho", nullptr);
940 s_lua_record_ctx
->bestwho
= dnsp
.getRemote();
942 lua
.writeVariable("bestwho", s_lua_record_ctx
->bestwho
);
946 if(!code
.empty() && code
[0]!=';')
947 actual
= "return " + code
;
949 actual
= code
.substr(1);
951 auto content
=lua
.executeCode
<boost::variant
<string
, vector
<pair
<int, string
> > > >(actual
);
953 vector
<string
> contents
;
954 if(auto str
= boost::get
<string
>(&content
))
955 contents
.push_back(*str
);
957 for(const auto& c
: boost::get
<vector
<pair
<int,string
>>>(content
))
958 contents
.push_back(c
.second
);
960 for(const auto& content_it
: contents
) {
961 if(qtype
==QType::TXT
)
962 ret
.push_back(DNSRecordContent::mastermake(qtype
, QClass::IN
, '"'+content_it
+'"' ));
964 ret
.push_back(DNSRecordContent::mastermake(qtype
, QClass::IN
, content_it
));
966 } catch(std::exception
&e
) {
967 g_log
<< Logger::Info
<< "Lua record ("<<query
<<"|"<<QType(qtype
).getName()<<") reported: " << e
.what();
969 std::rethrow_if_nested(e
);
971 } catch(const std::exception
& ne
) {
972 g_log
<< ": " << ne
.what() << std::endl
;
974 catch(const PDNSException
& ne
) {
975 g_log
<< ": " << ne
.reason
<< std::endl
;