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
});
347 ComboAddress::addressOnlyHash aoh
;
348 int r
= aoh(bestwho
) % sum
;
349 auto p
= upper_bound(pick
.begin(), pick
.end(), r
, [](int rarg
, const decltype(pick
)::value_type
& a
) { return rarg
< a
.first
; });
353 static bool getLatLon(const std::string
& ip
, double& lat
, double& lon
)
355 string inp
= getGeo(ip
, GeoIPInterface::Location
);
358 lat
=atof(inp
.c_str());
359 auto pos
=inp
.find(' ');
360 if(pos
!= string::npos
)
361 lon
=atof(inp
.c_str() + pos
);
365 static bool getLatLon(const std::string
& ip
, string
& loc
)
367 int latdeg
, latmin
, londeg
, lonmin
;
368 double latsec
, lonsec
;
369 char lathem
='X', lonhem
='X';
372 if(!getLatLon(ip
, lat
, lon
))
393 >>> min = int((R - int(R)) * 60.0)
394 >>> sec = (((R - int(R)) * 60.0) - min) * 60.0
395 >>> print("{}º {}' {}\"".format(deg, min, sec))
400 latmin
= (lat
- latdeg
)*60.0;
401 latsec
= (((lat
- latdeg
)*60.0) - latmin
)*60.0;
404 lonmin
= (lon
- londeg
)*60.0;
405 lonsec
= (((lon
- londeg
)*60.0) - lonmin
)*60.0;
407 // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
409 boost::format
fmt("%d %d %d %c %d %d %d %c 0.00m 1.00m 10000.00m 10.00m");
411 loc
= (fmt
% latdeg
% latmin
% latsec
% lathem
% londeg
% lonmin
% lonsec
% lonhem
).str();
415 static ComboAddress
pickclosest(const ComboAddress
& bestwho
, const vector
<ComboAddress
>& wips
)
418 throw std::invalid_argument("The IP list cannot be empty");
420 map
<double,vector
<ComboAddress
> > ranked
;
421 double wlat
=0, wlon
=0;
422 getLatLon(bestwho
.toString(), wlat
, wlon
);
423 // cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
425 for(const auto& c
: wips
) {
427 getLatLon(c
.toString(), lat
, lon
);
428 // cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
429 double latdiff
= wlat
-lat
;
430 double londiff
= wlon
-lon
;
432 londiff
= 360 - londiff
;
433 double dist2
=latdiff
*latdiff
+ londiff
*londiff
;
434 // cout<<" distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
435 ranked
[dist2
].push_back(c
);
437 return ranked
.begin()->second
[dns_random(ranked
.begin()->second
.size())];
440 static std::vector
<DNSZoneRecord
> lookup(const DNSName
& name
, uint16_t qtype
, int zoneid
)
442 static UeberBackend ub
;
443 static std::mutex mut
;
444 std::lock_guard
<std::mutex
> lock(mut
);
445 ub
.lookup(QType(qtype
), name
, zoneid
);
447 vector
<DNSZoneRecord
> ret
;
454 static std::string
getOptionValue(const boost::optional
<std::unordered_map
<string
, string
>>& options
, const std::string
&name
, const std::string
&defaultValue
)
456 string selector
=defaultValue
;
458 if(options
->count(name
))
459 selector
=options
->find(name
)->second
;
464 static vector
<ComboAddress
> useSelector(const std::string
&selector
, const ComboAddress
& bestwho
, const vector
<ComboAddress
>& candidates
)
466 vector
<ComboAddress
> ret
;
470 else if(selector
=="random")
471 ret
.emplace_back(pickrandom(candidates
));
472 else if(selector
=="pickclosest")
473 ret
.emplace_back(pickclosest(bestwho
, candidates
));
474 else if(selector
=="hashed")
475 ret
.emplace_back(hashed(bestwho
, candidates
));
477 g_log
<<Logger::Warning
<<"LUA Record called with unknown selector '"<<selector
<<"'"<<endl
;
478 ret
.emplace_back(pickrandom(candidates
));
484 static vector
<string
> convIpListToString(const vector
<ComboAddress
> &comboAddresses
)
488 for (const auto& c
: comboAddresses
) {
489 ret
.emplace_back(c
.toString());
495 static vector
<ComboAddress
> convIplist(const iplist_t
& src
)
497 vector
<ComboAddress
> ret
;
499 for(const auto& ip
: src
) {
500 ret
.emplace_back(ip
.second
);
506 static vector
<pair
<int, ComboAddress
> > convWIplist(std::unordered_map
<int, wiplist_t
> src
)
508 vector
<pair
<int,ComboAddress
> > ret
;
510 for(const auto& i
: src
) {
511 ret
.emplace_back(atoi(i
.second
.at(1).c_str()), ComboAddress(i
.second
.at(2)));
517 static thread_local unique_ptr
<AuthLua4
> s_LUA
;
518 bool g_LuaRecordSharedState
;
520 typedef struct AuthLuaRecordContext
522 ComboAddress bestwho
;
528 static thread_local unique_ptr
<lua_record_ctx_t
> s_lua_record_ctx
;
530 static void setupLuaRecords()
532 LuaContext
& lua
= *s_LUA
->getLua();
534 lua
.writeFunction("latlon", []() {
536 getLatLon(s_lua_record_ctx
->bestwho
.toString(), lat
, lon
);
537 return std::to_string(lat
)+" "+std::to_string(lon
);
539 lua
.writeFunction("latlonloc", []() {
541 getLatLon(s_lua_record_ctx
->bestwho
.toString(), loc
);
544 lua
.writeFunction("closestMagic", []() {
545 vector
<ComboAddress
> candidates
;
546 // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
547 for(auto l
: s_lua_record_ctx
->qname
.getRawLabels()) {
548 boost::replace_all(l
, "-", ".");
550 candidates
.emplace_back(l
);
551 } catch (const PDNSException
& e
) {
552 // no need to continue as we most likely reached the end of the ip list
556 return pickclosest(s_lua_record_ctx
->bestwho
, candidates
).toString();
558 lua
.writeFunction("latlonMagic", [](){
559 auto labels
= s_lua_record_ctx
->qname
.getRawLabels();
561 return std::string("unknown");
563 getLatLon(labels
[3]+"."+labels
[2]+"."+labels
[1]+"."+labels
[0], lat
, lon
);
564 return std::to_string(lat
)+" "+std::to_string(lon
);
568 lua
.writeFunction("createReverse", [](string suffix
, boost::optional
<std::unordered_map
<string
,string
>> e
){
570 auto labels
= s_lua_record_ctx
->qname
.getRawLabels();
572 return std::string("unknown");
574 vector
<ComboAddress
> candidates
;
576 // exceptions are relative to zone
577 // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
578 // e["1.2.3.4"]="bert.powerdns.com" - should match, easy enough to do
579 // the issue is with classless delegation..
581 ComboAddress
req(labels
[3]+"."+labels
[2]+"."+labels
[1]+"."+labels
[0], 0);
582 const auto& uom
= *e
;
583 for(const auto& c
: uom
)
584 if(ComboAddress(c
.first
, 0) == req
)
587 boost::format
fmt(suffix
);
588 fmt
.exceptions( boost::io::all_error_bits
^ ( boost::io::too_many_args_bit
| boost::io::too_few_args_bit
) );
589 fmt
% labels
[3] % labels
[2] % labels
[1] % labels
[0];
591 fmt
% (labels
[3]+"-"+labels
[2]+"-"+labels
[1]+"-"+labels
[0]);
593 boost::format
fmt2("%02x%02x%02x%02x");
594 for(int i
=3; i
>=0; --i
)
595 fmt2
% atoi(labels
[i
].c_str());
601 catch(std::exception
& ex
) {
602 g_log
<<Logger::Error
<<"error: "<<ex
.what()<<endl
;
604 return std::string("error");
606 lua
.writeFunction("createForward", []() {
607 DNSName rel
=s_lua_record_ctx
->qname
.makeRelative(s_lua_record_ctx
->zone
);
608 auto parts
= rel
.getRawLabels();
610 return parts
[0]+"."+parts
[1]+"."+parts
[2]+"."+parts
[3];
611 if(parts
.size()==1) {
612 // either hex string, or 12-13-14-15
613 // cout<<parts[0]<<endl;
614 unsigned int x1
, x2
, x3
, x4
;
615 if(sscanf(parts
[0].c_str()+2, "%02x%02x%02x%02x", &x1
, &x2
, &x3
, &x4
)==4) {
616 return std::to_string(x1
)+"."+std::to_string(x2
)+"."+std::to_string(x3
)+"."+std::to_string(x4
);
621 return std::string("0.0.0.0");
624 lua
.writeFunction("createForward6", []() {
625 DNSName rel
=s_lua_record_ctx
->qname
.makeRelative(s_lua_record_ctx
->zone
);
626 auto parts
= rel
.getRawLabels();
627 if(parts
.size()==8) {
629 for(int i
=0; i
<8; ++i
) {
634 ComboAddress
ca(tot
);
635 return ca
.toString();
637 else if(parts
.size()==1) {
638 boost::replace_all(parts
[0],"-",":");
639 ComboAddress
ca(parts
[0]);
640 return ca
.toString();
643 return std::string("::");
645 lua
.writeFunction("createReverse6", [](string suffix
, boost::optional
<std::unordered_map
<string
,string
>> e
){
646 vector
<ComboAddress
> candidates
;
649 auto labels
= s_lua_record_ctx
->qname
.getRawLabels();
651 return std::string("unknown");
652 boost::format
fmt(suffix
);
653 fmt
.exceptions( boost::io::all_error_bits
^ ( boost::io::too_many_args_bit
| boost::io::too_few_args_bit
) );
657 vector
<string
> quads
;
658 for(int i
=0; i
<8; ++i
) {
662 for(int j
=0; j
<4; ++j
) {
663 quad
.append(1, labels
[31-i
*4-j
][0]);
664 together
+= labels
[31-i
*4-j
][0];
666 quads
.push_back(quad
);
668 ComboAddress
ip6(together
,0);
672 for(const auto& addr
: addrs
) {
673 // this makes sure we catch all forms of the address
674 if(ComboAddress(addr
.first
,0)==ip6
)
679 string dashed
=ip6
.toString();
680 boost::replace_all(dashed
, ":", "-");
682 for(int i
=31; i
>=0; --i
)
686 for(const auto& quad
: quads
)
691 catch(std::exception
& ex
) {
692 g_log
<<Logger::Error
<<"LUA Record xception: "<<ex
.what()<<endl
;
694 catch(PDNSException
& ex
) {
695 g_log
<<Logger::Error
<<"LUA Record exception: "<<ex
.reason
<<endl
;
697 return std::string("unknown");
701 * Simplistic test to see if an IP address listens on a certain port
702 * Will return a single IP address from the set of available IP addresses. If
703 * no IP address is available, will return a random element of the set of
704 * addresses supplied for testing.
706 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
708 lua
.writeFunction("ifportup", [](int port
, const vector
<pair
<int, string
> >& ips
, const boost::optional
<std::unordered_map
<string
,string
>> options
) {
709 vector
<ComboAddress
> candidates
, unavailables
;
711 vector
<ComboAddress
> conv
;
712 std::string selector
;
716 for(const auto& i
: ips
) {
717 ComboAddress
rem(i
.second
, port
);
718 if(g_up
.isUp(rem
, opts
)) {
719 candidates
.push_back(rem
);
722 unavailables
.push_back(rem
);
725 if(!candidates
.empty()) {
726 // use regular selector
727 selector
= getOptionValue(options
, "selector", "random");
729 // All units are down, apply backupSelector on all candidates
730 candidates
= std::move(unavailables
);
731 selector
= getOptionValue(options
, "backupSelector", "random");
734 vector
<ComboAddress
> res
= useSelector(selector
, s_lua_record_ctx
->bestwho
, candidates
);
735 return convIpListToString(res
);
738 lua
.writeFunction("ifurlup", [](const std::string
& url
,
739 const boost::variant
<iplist_t
, ipunitlist_t
>& ips
,
740 boost::optional
<opts_t
> options
) {
741 vector
<vector
<ComboAddress
> > candidates
;
745 if(auto simple
= boost::get
<iplist_t
>(&ips
)) {
746 vector
<ComboAddress
> unit
= convIplist(*simple
);
747 candidates
.push_back(unit
);
749 auto units
= boost::get
<ipunitlist_t
>(ips
);
750 for(const auto& u
: units
) {
751 vector
<ComboAddress
> unit
= convIplist(u
.second
);
752 candidates
.push_back(unit
);
756 for(const auto& unit
: candidates
) {
757 vector
<ComboAddress
> available
;
758 for(const auto& c
: unit
) {
759 if(g_up
.isUp(c
, url
, opts
)) {
760 available
.push_back(c
);
763 if(!available
.empty()) {
764 vector
<ComboAddress
> res
= useSelector(getOptionValue(options
, "selector", "random"), s_lua_record_ctx
->bestwho
, available
);
765 return convIpListToString(res
);
769 // All units down, apply backupSelector on all candidates
770 vector
<ComboAddress
> ret
{};
771 for(const auto& unit
: candidates
) {
772 ret
.insert(ret
.end(), unit
.begin(), unit
.end());
775 vector
<ComboAddress
> res
= useSelector(getOptionValue(options
, "backupSelector", "random"), s_lua_record_ctx
->bestwho
, ret
);
776 return convIpListToString(res
);
779 * Returns a random IP address from the supplied list
780 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
782 lua
.writeFunction("pickrandom", [](const iplist_t
& ips
) {
783 vector
<ComboAddress
> conv
= convIplist(ips
);
785 return pickrandom(conv
).toString();
790 * Returns a random IP address from the supplied list, as weighted by the
791 * various ``weight`` parameters
792 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
794 lua
.writeFunction("pickwrandom", [](std::unordered_map
<int, wiplist_t
> ips
) {
795 vector
<pair
<int,ComboAddress
> > conv
= convWIplist(ips
);
797 return pickwrandom(conv
).toString();
801 * Based on the hash of `bestwho`, returns an IP address from the list
802 * supplied, as weighted by the various `weight` parameters
803 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
805 lua
.writeFunction("pickwhashed", [](std::unordered_map
<int, wiplist_t
> ips
) {
806 vector
<pair
<int,ComboAddress
> > conv
;
809 conv
.emplace_back(atoi(i
.second
[1].c_str()), ComboAddress(i
.second
[2]));
811 return pickwhashed(s_lua_record_ctx
->bestwho
, conv
).toString();
815 lua
.writeFunction("pickclosest", [](const iplist_t
& ips
) {
816 vector
<ComboAddress
> conv
= convIplist(ips
);
818 return pickclosest(s_lua_record_ctx
->bestwho
, conv
).toString();
822 if (g_luaRecordExecLimit
> 0) {
823 lua
.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit
));
826 lua
.writeFunction("report", [](string event
, boost::optional
<string
> line
){
827 throw std::runtime_error("Script took too long");
830 lua
.writeFunction("geoiplookup", [](const string
&ip
, const GeoIPInterface::GeoIPQueryAttribute attr
) {
831 return getGeo(ip
, attr
);
834 typedef const boost::variant
<string
,vector
<pair
<int,string
> > > combovar_t
;
835 lua
.writeFunction("continent", [](const combovar_t
& continent
) {
836 string res
=getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Continent
);
837 return doCompare(continent
, res
, [](const std::string
& a
, const std::string
& b
) {
838 return !strcasecmp(a
.c_str(), b
.c_str());
841 lua
.writeFunction("asnum", [](const combovar_t
& asns
) {
842 string res
=getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::ASn
);
843 return doCompare(asns
, res
, [](const std::string
& a
, const std::string
& b
) {
844 return !strcasecmp(a
.c_str(), b
.c_str());
847 lua
.writeFunction("country", [](const combovar_t
& var
) {
848 string res
= getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Country2
);
849 return doCompare(var
, res
, [](const std::string
& a
, const std::string
& b
) {
850 return !strcasecmp(a
.c_str(), b
.c_str());
854 lua
.writeFunction("netmask", [](const iplist_t
& ips
) {
855 for(const auto& i
:ips
) {
856 Netmask
nm(i
.second
);
857 if(nm
.match(s_lua_record_ctx
->bestwho
))
864 {'192.168.0.0/16', '10.0.0.0/8'},
865 {'192.168.20.20', '192.168.20.21'}
868 {'0.0.0.0/0'}, {'192.0.2.1'}
872 lua
.writeFunction("view", [](const vector
<pair
<int, vector
<pair
<int, iplist_t
> > > >& in
) {
873 for(const auto& rule
: in
) {
874 const auto& netmasks
=rule
.second
[0].second
;
875 const auto& destinations
=rule
.second
[1].second
;
876 for(const auto& nmpair
: netmasks
) {
877 Netmask
nm(nmpair
.second
);
878 if(nm
.match(s_lua_record_ctx
->bestwho
)) {
879 if (destinations
.empty()) {
880 throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm
.toString() + ")");
882 return destinations
[dns_random(destinations
.size())].second
;
886 return std::string();
891 lua
.writeFunction("include", [&lua
](string record
) {
893 vector
<DNSZoneRecord
> drs
= lookup(DNSName(record
) + s_lua_record_ctx
->zone
, QType::LUA
, s_lua_record_ctx
->zoneid
);
894 for(const auto& dr
: drs
) {
895 auto lr
= getRR
<LUARecordContent
>(dr
.dr
);
896 lua
.executeCode(lr
->getCode());
899 catch(std::exception
& e
) {
900 g_log
<<Logger::Error
<<"Failed to load include record for LUArecord "<<(DNSName(record
)+s_lua_record_ctx
->zone
)<<": "<<e
.what()<<endl
;
905 std::vector
<shared_ptr
<DNSRecordContent
>> luaSynth(const std::string
& code
, const DNSName
& query
, const DNSName
& zone
, int zoneid
, const DNSPacket
& dnsp
, uint16_t qtype
)
907 if(!s_LUA
|| // we don't have a Lua state yet
908 !g_LuaRecordSharedState
) { // or we want a new one even if we had one
909 s_LUA
= make_unique
<AuthLua4
>();
913 std::vector
<shared_ptr
<DNSRecordContent
>> ret
;
915 LuaContext
& lua
= *s_LUA
->getLua();
917 s_lua_record_ctx
= std::unique_ptr
<lua_record_ctx_t
>(new lua_record_ctx_t());
918 s_lua_record_ctx
->qname
= query
;
919 s_lua_record_ctx
->zone
= zone
;
920 s_lua_record_ctx
->zoneid
= zoneid
;
922 lua
.writeVariable("qname", query
);
923 lua
.writeVariable("zone", zone
);
924 lua
.writeVariable("zoneid", zoneid
);
925 lua
.writeVariable("who", dnsp
.getRemote());
926 lua
.writeVariable("dh", (dnsheader
*)&dnsp
.d
);
927 lua
.writeVariable("dnssecOK", dnsp
.d_dnssecOk
);
928 lua
.writeVariable("tcp", dnsp
.d_tcp
);
929 lua
.writeVariable("ednsPKTSize", dnsp
.d_ednsRawPacketSizeLimit
);
930 if(dnsp
.hasEDNSSubnet()) {
931 lua
.writeVariable("ecswho", dnsp
.getRealRemote());
932 s_lua_record_ctx
->bestwho
= dnsp
.getRealRemote().getNetwork();
935 lua
.writeVariable("ecswho", nullptr);
936 s_lua_record_ctx
->bestwho
= dnsp
.getRemote();
938 lua
.writeVariable("bestwho", s_lua_record_ctx
->bestwho
);
942 if(!code
.empty() && code
[0]!=';')
943 actual
= "return " + code
;
945 actual
= code
.substr(1);
947 auto content
=lua
.executeCode
<boost::variant
<string
, vector
<pair
<int, string
> > > >(actual
);
949 vector
<string
> contents
;
950 if(auto str
= boost::get
<string
>(&content
))
951 contents
.push_back(*str
);
953 for(const auto& c
: boost::get
<vector
<pair
<int,string
>>>(content
))
954 contents
.push_back(c
.second
);
956 for(const auto& content_it
: contents
) {
957 if(qtype
==QType::TXT
)
958 ret
.push_back(DNSRecordContent::mastermake(qtype
, QClass::IN
, '"'+content_it
+'"' ));
960 ret
.push_back(DNSRecordContent::mastermake(qtype
, QClass::IN
, content_it
));
962 } catch(std::exception
&e
) {
963 g_log
<< Logger::Info
<< "Lua record ("<<query
<<"|"<<QType(qtype
).getName()<<") reported: " << e
.what();
965 std::rethrow_if_nested(e
);
967 } catch(const std::exception
& ne
) {
968 g_log
<< ": " << ne
.what() << std::endl
;
970 catch(const PDNSException
& ne
) {
971 g_log
<< ": " << ne
.reason
<< std::endl
;