3 #include <boost/format.hpp>
9 #include "ext/luawrapper/include/LuaContext.hpp"
11 #include "lua-auth4.hh"
13 #include "minicurl.hh"
14 #include "ueberbackend.hh"
15 #include "dnsrecords.hh"
16 #include "dns_random.hh"
17 #include "auth-main.hh"
18 #include "../modules/geoipbackend/geoipinterface.hh" // only for the enum
21 block AXFR unless TSIG, or override
25 check the wildcard 'no cache' stuff, we may get it wrong
27 ponder ECS scopemask setting
29 ponder netmask tree from file for huge number of netmasks
31 unify ifurlup/ifportup
32 add attribute for certificate check
33 add list of current monitors
36 pool of UeberBackends?
41 extern int g_luaRecordExecLimit
;
43 using iplist_t
= vector
<pair
<int, string
> >;
44 using wiplist_t
= std::unordered_map
<int, string
>;
45 using ipunitlist_t
= vector
<pair
<int, iplist_t
> >;
46 using opts_t
= std::unordered_map
<string
,string
>;
56 bool operator<(const CheckDesc
& rhs
) const
58 std::map
<string
,string
> oopts
, rhsoopts
;
59 for(const auto& m
: opts
)
60 oopts
[m
.first
]=m
.second
;
61 for(const auto& m
: rhs
.opts
)
62 rhsoopts
[m
.first
]=m
.second
;
64 return std::tuple(rem
, url
, oopts
) <
65 std::tuple(rhs
.rem
, rhs
.url
, rhsoopts
);
70 CheckState(time_t _lastAccess
): lastAccess(_lastAccess
) {}
72 std::atomic
<bool> status
{false};
74 std::atomic
<bool> first
{true};
75 /* last time the status was accessed */
76 std::atomic
<time_t> lastAccess
{0};
82 d_checkerThreadStarted
.clear();
84 ~IsUpOracle() = default;
85 bool isUp(const ComboAddress
& remote
, const opts_t
& opts
);
86 bool isUp(const ComboAddress
& remote
, const std::string
& url
, const opts_t
& opts
);
87 bool isUp(const CheckDesc
& cd
);
90 void checkURL(const CheckDesc
& cd
, const bool status
, const bool first
= false)
95 if (cd
.opts
.count("timeout")) {
96 timeout
= std::atoi(cd
.opts
.at("timeout").c_str());
98 string useragent
= productName();
99 if (cd
.opts
.count("useragent")) {
100 useragent
= cd
.opts
.at("useragent");
102 size_t byteslimit
= 0;
103 if (cd
.opts
.count("byteslimit")) {
104 byteslimit
= static_cast<size_t>(std::atoi(cd
.opts
.at("byteslimit").c_str()));
106 MiniCurl
mc(useragent
);
109 const ComboAddress
* rem
= nullptr;
110 if(cd
.rem
.sin4
.sin_family
!= AF_UNSPEC
) {
112 remstring
= rem
->toString();
114 remstring
= "[externally checked IP]";
117 if (cd
.opts
.count("source")) {
118 ComboAddress
src(cd
.opts
.at("source"));
119 content
=mc
.getURL(cd
.url
, rem
, &src
, timeout
, false, false, byteslimit
);
122 content
=mc
.getURL(cd
.url
, rem
, nullptr, timeout
, false, false, byteslimit
);
124 if (cd
.opts
.count("stringmatch") && content
.find(cd
.opts
.at("stringmatch")) == string::npos
) {
125 throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % cd
.opts
.at("stringmatch")));
129 g_log
<<Logger::Info
<<"LUA record monitoring declaring "<<remstring
<<" UP for URL "<<cd
.url
<<"!"<<endl
;
133 catch(std::exception
& ne
) {
135 g_log
<<Logger::Info
<<"LUA record monitoring declaring "<<remstring
<<" DOWN for URL "<<cd
.url
<<", error: "<<ne
.what()<<endl
;
139 void checkTCP(const CheckDesc
& cd
, const bool status
, const bool first
= false) {
142 if (cd
.opts
.count("timeout")) {
143 timeout
= std::atoi(cd
.opts
.at("timeout").c_str());
145 Socket
s(cd
.rem
.sin4
.sin_family
, SOCK_STREAM
);
148 if (cd
.opts
.count("source")) {
149 src
= ComboAddress(cd
.opts
.at("source"));
152 s
.connect(cd
.rem
, timeout
);
154 g_log
<<Logger::Info
<<"Lua record monitoring declaring TCP/IP "<<cd
.rem
.toStringWithPort()<<" ";
155 if(cd
.opts
.count("source"))
156 g_log
<<"(source "<<src
.toString()<<") ";
161 catch (const NetworkError
& ne
) {
162 if(status
|| first
) {
163 g_log
<<Logger::Info
<<"Lua record monitoring declaring TCP/IP "<<cd
.rem
.toStringWithPort()<<" DOWN: "<<ne
.what()<<endl
;
172 std::chrono::system_clock::time_point checkStart
= std::chrono::system_clock::now();
173 std::vector
<std::future
<void>> results
;
174 std::vector
<CheckDesc
> toDelete
;
176 // make sure there's no insertion
177 auto statuses
= d_statuses
.read_lock();
178 for (auto& it
: *statuses
) {
179 auto& desc
= it
.first
;
180 auto& state
= it
.second
;
182 if (desc
.url
.empty()) { // TCP
183 results
.push_back(std::async(std::launch::async
, &IsUpOracle::checkTCP
, this, desc
, state
->status
.load(), state
->first
.load()));
185 results
.push_back(std::async(std::launch::async
, &IsUpOracle::checkURL
, this, desc
, state
->status
.load(), state
->first
.load()));
187 if (std::chrono::system_clock::from_time_t(state
->lastAccess
) < (checkStart
- std::chrono::seconds(g_luaHealthChecksExpireDelay
))) {
188 toDelete
.push_back(desc
);
192 // we can release the lock as nothing will be deleted
193 for (auto& future
: results
) {
196 if (!toDelete
.empty()) {
197 auto statuses
= d_statuses
.write_lock();
198 for (auto& it
: toDelete
) {
202 std::this_thread::sleep_until(checkStart
+ std::chrono::seconds(g_luaHealthChecksInterval
));
206 typedef map
<CheckDesc
, std::unique_ptr
<CheckState
>> statuses_t
;
207 SharedLockGuarded
<statuses_t
> d_statuses
;
209 std::unique_ptr
<std::thread
> d_checkerThread
;
210 std::atomic_flag d_checkerThreadStarted
;
212 void setStatus(const CheckDesc
& cd
, bool status
)
214 auto statuses
= d_statuses
.write_lock();
215 auto& state
= (*statuses
)[cd
];
216 state
->status
= status
;
218 state
->first
= false;
222 void setDown(const ComboAddress
& rem
, const std::string
& url
=std::string(), const opts_t
& opts
= opts_t())
224 CheckDesc cd
{rem
, url
, opts
};
225 setStatus(cd
, false);
228 void setUp(const ComboAddress
& rem
, const std::string
& url
=std::string(), const opts_t
& opts
= opts_t())
230 CheckDesc cd
{rem
, url
, opts
};
235 void setDown(const CheckDesc
& cd
)
237 setStatus(cd
, false);
240 void setUp(const CheckDesc
& cd
)
246 bool IsUpOracle::isUp(const CheckDesc
& cd
)
248 if (!d_checkerThreadStarted
.test_and_set()) {
249 d_checkerThread
= std::make_unique
<std::thread
>([this] { return checkThread(); });
251 time_t now
= time(nullptr);
253 auto statuses
= d_statuses
.read_lock();
254 auto iter
= statuses
->find(cd
);
255 if (iter
!= statuses
->end()) {
256 iter
->second
->lastAccess
= now
;
257 return iter
->second
->status
;
260 // try to parse options so we don't insert any malformed content
261 if (cd
.opts
.count("source")) {
262 ComboAddress
src(cd
.opts
.at("source"));
265 auto statuses
= d_statuses
.write_lock();
266 // Make sure we don't insert new entry twice now we have the lock
267 if (statuses
->find(cd
) == statuses
->end()) {
268 (*statuses
)[cd
] = std::make_unique
<CheckState
>(now
);
274 bool IsUpOracle::isUp(const ComboAddress
& remote
, const opts_t
& opts
)
276 CheckDesc cd
{remote
, "", opts
};
280 bool IsUpOracle::isUp(const ComboAddress
& remote
, const std::string
& url
, const opts_t
& opts
)
282 CheckDesc cd
{remote
, url
, opts
};
288 template<typename T
, typename C
>
289 bool doCompare(const T
& var
, const std::string
& res
, const C
& cmp
)
291 if(auto country
= boost::get
<string
>(&var
))
292 return cmp(*country
, res
);
294 auto countries
=boost::get
<vector
<pair
<int,string
> > >(&var
);
295 for(const auto& country
: *countries
) {
296 if(cmp(country
.second
, res
))
303 static std::string
getGeo(const std::string
& ip
, GeoIPInterface::GeoIPQueryAttribute qa
)
305 static bool initialized
;
306 extern std::function
<std::string(const std::string
& ip
, int)> g_getGeo
;
309 g_log
<<Logger::Error
<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl
;
315 return g_getGeo(ip
, (int)qa
);
318 template <typename T
>
319 static T
pickRandom(const vector
<T
>& items
)
322 throw std::invalid_argument("The items list cannot be empty");
324 return items
[dns_random(items
.size())];
327 template <typename T
>
328 static T
pickHashed(const ComboAddress
& who
, const vector
<T
>& items
)
331 throw std::invalid_argument("The items list cannot be empty");
333 ComboAddress::addressOnlyHash aoh
;
334 return items
[aoh(who
) % items
.size()];
337 template <typename T
>
338 static T
pickWeightedRandom(const vector
< pair
<int, T
> >& items
)
341 throw std::invalid_argument("The items list cannot be empty");
344 vector
< pair
<int, T
> > pick
;
345 pick
.reserve(items
.size());
347 for(auto& i
: items
) {
349 pick
.emplace_back(sum
, i
.second
);
353 throw std::invalid_argument("The sum of items cannot be zero");
356 int r
= dns_random(sum
);
357 auto p
= upper_bound(pick
.begin(), pick
.end(), r
, [](int rarg
, const typename
decltype(pick
)::value_type
& a
) { return rarg
< a
.first
; });
361 template <typename T
>
362 static T
pickWeightedHashed(const ComboAddress
& bestwho
, vector
< pair
<int, T
> >& items
)
365 throw std::invalid_argument("The items list cannot be empty");
368 vector
< pair
<int, T
> > pick
;
369 pick
.reserve(items
.size());
371 for(auto& i
: items
) {
373 pick
.push_back({sum
, i
.second
});
377 throw std::invalid_argument("The sum of items cannot be zero");
380 ComboAddress::addressOnlyHash aoh
;
381 int r
= aoh(bestwho
) % sum
;
382 auto p
= upper_bound(pick
.begin(), pick
.end(), r
, [](int rarg
, const typename
decltype(pick
)::value_type
& a
) { return rarg
< a
.first
; });
386 template <typename T
>
387 static vector
<T
> pickRandomSample(int n
, const vector
<T
>& items
)
390 throw std::invalid_argument("The items list cannot be empty");
394 pick
.reserve(items
.size());
396 for(auto& item
: items
) {
397 pick
.push_back(item
);
400 int count
= std::min(std::max
<size_t>(0, n
), items
.size());
406 std::shuffle(pick
.begin(), pick
.end(), pdns::dns_random_engine());
408 vector
<T
> result
= {pick
.begin(), pick
.begin() + count
};
412 static bool getLatLon(const std::string
& ip
, double& lat
, double& lon
)
414 string inp
= getGeo(ip
, GeoIPInterface::Location
);
417 lat
=atof(inp
.c_str());
418 auto pos
=inp
.find(' ');
419 if(pos
!= string::npos
)
420 lon
=atof(inp
.c_str() + pos
);
424 static bool getLatLon(const std::string
& ip
, string
& loc
)
426 int latdeg
, latmin
, londeg
, lonmin
;
427 double latsec
, lonsec
;
428 char lathem
='X', lonhem
='X';
430 double lat
= 0, lon
= 0;
431 if(!getLatLon(ip
, lat
, lon
))
451 latmin
= (lat
- latdeg
)*60.0;
452 latsec
= (((lat
- latdeg
)*60.0) - latmin
)*60.0;
455 lonmin
= (lon
- londeg
)*60.0;
456 lonsec
= (((lon
- londeg
)*60.0) - lonmin
)*60.0;
458 // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
460 boost::format
fmt("%d %d %d %c %d %d %d %c 0.00m 1.00m 10000.00m 10.00m");
462 loc
= (fmt
% latdeg
% latmin
% latsec
% lathem
% londeg
% lonmin
% lonsec
% lonhem
).str();
466 static ComboAddress
pickclosest(const ComboAddress
& bestwho
, const vector
<ComboAddress
>& wips
)
469 throw std::invalid_argument("The IP list cannot be empty");
471 map
<double, vector
<ComboAddress
> > ranked
;
472 double wlat
=0, wlon
=0;
473 getLatLon(bestwho
.toString(), wlat
, wlon
);
474 // cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
476 for(const auto& c
: wips
) {
478 getLatLon(c
.toString(), lat
, lon
);
479 // cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
480 double latdiff
= wlat
-lat
;
481 double londiff
= wlon
-lon
;
483 londiff
= 360 - londiff
;
484 double dist2
=latdiff
*latdiff
+ londiff
*londiff
;
485 // cout<<" distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
486 ranked
[dist2
].push_back(c
);
488 return ranked
.begin()->second
[dns_random(ranked
.begin()->second
.size())];
491 static std::vector
<DNSZoneRecord
> lookup(const DNSName
& name
, uint16_t qtype
, int zoneid
)
493 static LockGuarded
<UeberBackend
> s_ub
;
496 vector
<DNSZoneRecord
> ret
;
498 auto ub
= s_ub
.lock();
499 ub
->lookup(QType(qtype
), name
, zoneid
);
500 while (ub
->get(dr
)) {
507 static bool getAuth(const DNSName
& name
, uint16_t qtype
, SOAData
* soaData
)
509 static LockGuarded
<UeberBackend
> s_ub
;
512 auto ueback
= s_ub
.lock();
513 return ueback
->getAuth(name
, qtype
, soaData
);
517 static std::string
getOptionValue(const boost::optional
<std::unordered_map
<string
, string
>>& options
, const std::string
&name
, const std::string
&defaultValue
)
519 string selector
=defaultValue
;
521 if(options
->count(name
))
522 selector
=options
->find(name
)->second
;
527 static vector
<ComboAddress
> useSelector(const std::string
&selector
, const ComboAddress
& bestwho
, const vector
<ComboAddress
>& candidates
)
529 vector
<ComboAddress
> ret
;
533 else if(selector
=="random")
534 ret
.emplace_back(pickRandom
<ComboAddress
>(candidates
));
535 else if(selector
=="pickclosest")
536 ret
.emplace_back(pickclosest(bestwho
, candidates
));
537 else if(selector
=="hashed")
538 ret
.emplace_back(pickHashed
<ComboAddress
>(bestwho
, candidates
));
540 g_log
<<Logger::Warning
<<"LUA Record called with unknown selector '"<<selector
<<"'"<<endl
;
541 ret
.emplace_back(pickRandom
<ComboAddress
>(candidates
));
547 static vector
<string
> convComboAddressListToString(const vector
<ComboAddress
>& items
)
549 vector
<string
> result
;
550 result
.reserve(items
.size());
552 for (const auto& item
: items
) {
553 result
.emplace_back(item
.toString());
559 static vector
<ComboAddress
> convComboAddressList(const iplist_t
& items
, uint16_t port
=0)
561 vector
<ComboAddress
> result
;
562 result
.reserve(items
.size());
564 for(const auto& item
: items
) {
565 result
.emplace_back(ComboAddress(item
.second
, port
));
572 * Reads and unify single or multiple sets of ips :
573 * - {'192.0.2.1', '192.0.2.2'}
574 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
577 static vector
<vector
<ComboAddress
>> convMultiComboAddressList(const boost::variant
<iplist_t
, ipunitlist_t
>& items
, uint16_t port
= 0)
579 vector
<vector
<ComboAddress
>> candidates
;
581 if(auto simple
= boost::get
<iplist_t
>(&items
)) {
582 vector
<ComboAddress
> unit
= convComboAddressList(*simple
, port
);
583 candidates
.push_back(unit
);
585 auto units
= boost::get
<ipunitlist_t
>(items
);
586 for(const auto& u
: units
) {
587 vector
<ComboAddress
> unit
= convComboAddressList(u
.second
, port
);
588 candidates
.push_back(unit
);
594 static vector
<string
> convStringList(const iplist_t
& items
)
596 vector
<string
> result
;
597 result
.reserve(items
.size());
599 for(const auto& item
: items
) {
600 result
.emplace_back(item
.second
);
606 static vector
< pair
<int, string
> > convIntStringPairList(const std::unordered_map
<int, wiplist_t
>& items
)
608 vector
<pair
<int,string
> > result
;
609 result
.reserve(items
.size());
611 for(const auto& item
: items
) {
612 result
.emplace_back(atoi(item
.second
.at(1).c_str()), item
.second
.at(2));
618 bool g_LuaRecordSharedState
;
620 typedef struct AuthLuaRecordContext
622 ComboAddress bestwho
;
628 static thread_local unique_ptr
<lua_record_ctx_t
> s_lua_record_ctx
;
630 static vector
<string
> genericIfUp(const boost::variant
<iplist_t
, ipunitlist_t
>& ips
, boost::optional
<opts_t
> options
, const std::function
<bool(const ComboAddress
&, const opts_t
&)>& upcheckf
, uint16_t port
= 0)
632 vector
<vector
<ComboAddress
> > candidates
;
637 candidates
= convMultiComboAddressList(ips
, port
);
639 for(const auto& unit
: candidates
) {
640 vector
<ComboAddress
> available
;
641 for(const auto& c
: unit
) {
642 if (upcheckf(c
, opts
)) {
643 available
.push_back(c
);
646 if(!available
.empty()) {
647 vector
<ComboAddress
> res
= useSelector(getOptionValue(options
, "selector", "random"), s_lua_record_ctx
->bestwho
, available
);
648 return convComboAddressListToString(res
);
652 // All units down, apply backupSelector on all candidates
653 vector
<ComboAddress
> ret
{};
654 for(const auto& unit
: candidates
) {
655 ret
.insert(ret
.end(), unit
.begin(), unit
.end());
658 vector
<ComboAddress
> res
= useSelector(getOptionValue(options
, "backupSelector", "random"), s_lua_record_ctx
->bestwho
, ret
);
659 return convComboAddressListToString(res
);
662 static void setupLuaRecords(LuaContext
& lua
)
664 lua
.writeFunction("latlon", []() {
665 double lat
= 0, lon
= 0;
666 getLatLon(s_lua_record_ctx
->bestwho
.toString(), lat
, lon
);
667 return std::to_string(lat
)+" "+std::to_string(lon
);
669 lua
.writeFunction("latlonloc", []() {
671 getLatLon(s_lua_record_ctx
->bestwho
.toString(), loc
);
674 lua
.writeFunction("closestMagic", []() {
675 vector
<ComboAddress
> candidates
;
676 // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
677 for(auto l
: s_lua_record_ctx
->qname
.getRawLabels()) {
678 boost::replace_all(l
, "-", ".");
680 candidates
.emplace_back(l
);
681 } catch (const PDNSException
& e
) {
682 // no need to continue as we most likely reached the end of the ip list
686 return pickclosest(s_lua_record_ctx
->bestwho
, candidates
).toString();
688 lua
.writeFunction("latlonMagic", [](){
689 auto labels
= s_lua_record_ctx
->qname
.getRawLabels();
691 return std::string("unknown");
692 double lat
= 0, lon
= 0;
693 getLatLon(labels
[3]+"."+labels
[2]+"."+labels
[1]+"."+labels
[0], lat
, lon
);
694 return std::to_string(lat
)+" "+std::to_string(lon
);
698 lua
.writeFunction("createReverse", [](string format
, boost::optional
<std::unordered_map
<string
,string
>> e
){
700 auto labels
= s_lua_record_ctx
->qname
.getRawLabels();
702 return std::string("unknown");
704 vector
<ComboAddress
> candidates
;
706 // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
707 // e["1.2.3.4"]="bert.powerdns.com" then provides an exception
709 ComboAddress
req(labels
[3]+"."+labels
[2]+"."+labels
[1]+"."+labels
[0], 0);
710 const auto& uom
= *e
;
711 for(const auto& c
: uom
)
712 if(ComboAddress(c
.first
, 0) == req
)
715 boost::format
fmt(format
);
716 fmt
.exceptions( boost::io::all_error_bits
^ ( boost::io::too_many_args_bit
| boost::io::too_few_args_bit
) );
717 fmt
% labels
[3] % labels
[2] % labels
[1] % labels
[0];
719 fmt
% (labels
[3]+"-"+labels
[2]+"-"+labels
[1]+"-"+labels
[0]);
721 boost::format
fmt2("%02x%02x%02x%02x");
722 for(int i
=3; i
>=0; --i
)
723 fmt2
% atoi(labels
[i
].c_str());
729 catch(std::exception
& ex
) {
730 g_log
<<Logger::Error
<<"error: "<<ex
.what()<<endl
;
732 return std::string("error");
734 lua
.writeFunction("createForward", []() {
735 static string
allZerosIP("0.0.0.0");
736 DNSName rel
=s_lua_record_ctx
->qname
.makeRelative(s_lua_record_ctx
->zone
);
737 // parts is something like ["1", "2", "3", "4", "static"] or
738 // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
739 auto parts
= rel
.getRawLabels();
740 // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
741 if(parts
.size()>=4) {
743 ComboAddress
ca(parts
[0]+"."+parts
[1]+"."+parts
[2]+"."+parts
[3]);
744 return ca
.toString();
745 } catch (const PDNSException
&e
) {
748 } else if (parts
.size() >= 1) {
749 // either hex string, or 12-13-14-15
750 vector
<string
> ip_parts
;
751 stringtok(ip_parts
, parts
[0], "-");
752 unsigned int x1
, x2
, x3
, x4
;
753 if (ip_parts
.size() >= 4) {
754 // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
756 for (size_t n
=4; n
> 0; n
--) {
757 auto octet
= ip_parts
[ip_parts
.size() - n
];
759 auto octetVal
= std::stol(octet
);
760 if (octetVal
>= 0 && octetVal
<= 255) {
761 ret
+= ip_parts
.at(ip_parts
.size() - n
) + ".";
765 } catch (const std::exception
&e
) {
769 ret
.resize(ret
.size() - 1); // remove trailing dot after last octet
771 } else if(parts
[0].length() >= 8 && sscanf(parts
[0].c_str()+(parts
[0].length()-8), "%02x%02x%02x%02x", &x1
, &x2
, &x3
, &x4
)==4) {
772 return std::to_string(x1
)+"."+std::to_string(x2
)+"."+std::to_string(x3
)+"."+std::to_string(x4
);
778 lua
.writeFunction("createForward6", []() {
779 DNSName rel
=s_lua_record_ctx
->qname
.makeRelative(s_lua_record_ctx
->zone
);
780 auto parts
= rel
.getRawLabels();
781 if(parts
.size()==8) {
783 for(int i
=0; i
<8; ++i
) {
788 ComboAddress
ca(tot
);
789 return ca
.toString();
791 else if(parts
.size()==1) {
792 if (parts
[0].find('-') != std::string::npos
) {
793 boost::replace_all(parts
[0],"-",":");
794 ComboAddress
ca(parts
[0]);
795 return ca
.toString();
797 if (parts
[0].size() >= 32) {
798 auto ippart
= parts
[0].substr(parts
[0].size()-32);
800 ippart
.substr(0, 4) + ":" +
801 ippart
.substr(4, 4) + ":" +
802 ippart
.substr(8, 4) + ":" +
803 ippart
.substr(12, 4) + ":" +
804 ippart
.substr(16, 4) + ":" +
805 ippart
.substr(20, 4) + ":" +
806 ippart
.substr(24, 4) + ":" +
807 ippart
.substr(28, 4);
809 ComboAddress
ca(fulladdress
);
810 return ca
.toString();
815 return std::string("::");
817 lua
.writeFunction("createReverse6", [](string format
, boost::optional
<std::unordered_map
<string
,string
>> e
){
818 vector
<ComboAddress
> candidates
;
821 auto labels
= s_lua_record_ctx
->qname
.getRawLabels();
823 return std::string("unknown");
824 boost::format
fmt(format
);
825 fmt
.exceptions( boost::io::all_error_bits
^ ( boost::io::too_many_args_bit
| boost::io::too_few_args_bit
) );
829 vector
<string
> quads
;
830 for(int i
=0; i
<8; ++i
) {
834 for(int j
=0; j
<4; ++j
) {
835 lquad
.append(1, labels
[31-i
*4-j
][0]);
836 together
+= labels
[31-i
*4-j
][0];
838 quads
.push_back(lquad
);
840 ComboAddress
ip6(together
,0);
844 for(const auto& addr
: addrs
) {
845 // this makes sure we catch all forms of the address
846 if(ComboAddress(addr
.first
,0)==ip6
)
851 string dashed
=ip6
.toString();
852 boost::replace_all(dashed
, ":", "-");
854 for(int i
=31; i
>=0; --i
)
858 for(const auto& lquad
: quads
)
863 catch(std::exception
& ex
) {
864 g_log
<<Logger::Error
<<"LUA Record exception: "<<ex
.what()<<endl
;
866 catch(PDNSException
& ex
) {
867 g_log
<<Logger::Error
<<"LUA Record exception: "<<ex
.reason
<<endl
;
869 return std::string("unknown");
872 lua
.writeFunction("filterForward", [](string address
, NetmaskGroup
& nmg
, boost::optional
<string
> fallback
) {
873 ComboAddress
ca(address
);
875 if (nmg
.match(ComboAddress(address
))) {
883 return string("0.0.0.0");
891 * Simplistic test to see if an IP address listens on a certain port
892 * Will return a single IP address from the set of available IP addresses. If
893 * no IP address is available, will return a random element of the set of
894 * addresses supplied for testing.
896 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
898 lua
.writeFunction("ifportup", [](int port
, const boost::variant
<iplist_t
, ipunitlist_t
>& ips
, const boost::optional
<std::unordered_map
<string
,string
>> options
) {
902 if (port
> std::numeric_limits
<uint16_t>::max()) {
903 port
= std::numeric_limits
<uint16_t>::max();
906 auto checker
= [](const ComboAddress
& addr
, const opts_t
& opts
) {
907 return g_up
.isUp(addr
, opts
);
909 return genericIfUp(ips
, options
, checker
, port
);
912 lua
.writeFunction("ifurlextup", [](const vector
<pair
<int, opts_t
> >& ipurls
, boost::optional
<opts_t
> options
) {
913 vector
<ComboAddress
> candidates
;
918 ComboAddress ca_unspec
;
919 ca_unspec
.sin4
.sin_family
=AF_UNSPEC
;
921 // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
922 for (const auto& [count
, unitmap
] : ipurls
) {
923 // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
924 vector
<ComboAddress
> available
;
926 for (const auto& [ipStr
, url
] : unitmap
) {
927 // unit: ["192.0.2.1"] = "https://example.com"
928 ComboAddress
ip(ipStr
);
929 candidates
.push_back(ip
);
930 if (g_up
.isUp(ca_unspec
, url
, opts
)) {
931 available
.push_back(ip
);
934 if(!available
.empty()) {
935 vector
<ComboAddress
> res
= useSelector(getOptionValue(options
, "selector", "random"), s_lua_record_ctx
->bestwho
, available
);
936 return convComboAddressListToString(res
);
940 // All units down, apply backupSelector on all candidates
941 vector
<ComboAddress
> res
= useSelector(getOptionValue(options
, "backupSelector", "random"), s_lua_record_ctx
->bestwho
, candidates
);
942 return convComboAddressListToString(res
);
945 lua
.writeFunction("ifurlup", [](const std::string
& url
,
946 const boost::variant
<iplist_t
, ipunitlist_t
>& ips
,
947 boost::optional
<opts_t
> options
) {
949 auto checker
= [&url
](const ComboAddress
& addr
, const opts_t
& opts
) {
950 return g_up
.isUp(addr
, url
, opts
);
952 return genericIfUp(ips
, options
, checker
);
955 * Returns a random IP address from the supplied list
956 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
958 lua
.writeFunction("pickrandom", [](const iplist_t
& ips
) {
959 vector
<string
> items
= convStringList(ips
);
960 return pickRandom
<string
>(items
);
963 lua
.writeFunction("pickrandomsample", [](int n
, const iplist_t
& ips
) {
964 vector
<string
> items
= convStringList(ips
);
965 return pickRandomSample
<string
>(n
, items
);
968 lua
.writeFunction("pickhashed", [](const iplist_t
& ips
) {
969 vector
<string
> items
= convStringList(ips
);
970 return pickHashed
<string
>(s_lua_record_ctx
->bestwho
, items
);
973 * Returns a random IP address from the supplied list, as weighted by the
974 * various ``weight`` parameters
975 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
977 lua
.writeFunction("pickwrandom", [](std::unordered_map
<int, wiplist_t
> ips
) {
978 vector
< pair
<int, string
> > items
= convIntStringPairList(ips
);
979 return pickWeightedRandom
<string
>(items
);
983 * Based on the hash of `bestwho`, returns an IP address from the list
984 * supplied, as weighted by the various `weight` parameters
985 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
987 lua
.writeFunction("pickwhashed", [](std::unordered_map
<int, wiplist_t
> ips
) {
988 vector
< pair
<int, string
> > items
;
990 items
.reserve(ips
.size());
992 items
.emplace_back(atoi(i
.second
[1].c_str()), i
.second
[2]);
994 return pickWeightedHashed
<string
>(s_lua_record_ctx
->bestwho
, items
);
998 lua
.writeFunction("pickclosest", [](const iplist_t
& ips
) {
999 vector
<ComboAddress
> conv
= convComboAddressList(ips
);
1001 return pickclosest(s_lua_record_ctx
->bestwho
, conv
).toString();
1005 if (g_luaRecordExecLimit
> 0) {
1006 lua
.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit
));
1009 lua
.writeFunction("report", [](string
/* event */, boost::optional
<string
> /* line */){
1010 throw std::runtime_error("Script took too long");
1013 lua
.writeFunction("geoiplookup", [](const string
&ip
, const GeoIPInterface::GeoIPQueryAttribute attr
) {
1014 return getGeo(ip
, attr
);
1017 typedef const boost::variant
<string
,vector
<pair
<int,string
> > > combovar_t
;
1019 lua
.writeFunction("asnum", [](const combovar_t
& asns
) {
1020 string res
=getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::ASn
);
1021 return doCompare(asns
, res
, [](const std::string
& a
, const std::string
& b
) {
1022 return !strcasecmp(a
.c_str(), b
.c_str());
1025 lua
.writeFunction("continent", [](const combovar_t
& continent
) {
1026 string res
=getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Continent
);
1027 return doCompare(continent
, res
, [](const std::string
& a
, const std::string
& b
) {
1028 return !strcasecmp(a
.c_str(), b
.c_str());
1031 lua
.writeFunction("continentCode", []() {
1032 string
unknown("unknown");
1033 string res
= getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Continent
);
1034 if ( res
== unknown
) {
1035 return std::string("--");
1039 lua
.writeFunction("country", [](const combovar_t
& var
) {
1040 string res
= getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Country2
);
1041 return doCompare(var
, res
, [](const std::string
& a
, const std::string
& b
) {
1042 return !strcasecmp(a
.c_str(), b
.c_str());
1046 lua
.writeFunction("countryCode", []() {
1047 string
unknown("unknown");
1048 string res
= getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Country2
);
1049 if ( res
== unknown
) {
1050 return std::string("--");
1054 lua
.writeFunction("region", [](const combovar_t
& var
) {
1055 string res
= getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Region
);
1056 return doCompare(var
, res
, [](const std::string
& a
, const std::string
& b
) {
1057 return !strcasecmp(a
.c_str(), b
.c_str());
1061 lua
.writeFunction("regionCode", []() {
1062 string
unknown("unknown");
1063 string res
= getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Region
);
1064 if ( res
== unknown
) {
1065 return std::string("--");
1069 lua
.writeFunction("netmask", [](const iplist_t
& ips
) {
1070 for(const auto& i
:ips
) {
1071 Netmask
nm(i
.second
);
1072 if(nm
.match(s_lua_record_ctx
->bestwho
))
1079 {'192.168.0.0/16', '10.0.0.0/8'},
1080 {'192.168.20.20', '192.168.20.21'}
1083 {'0.0.0.0/0'}, {'192.0.2.1'}
1087 lua
.writeFunction("view", [](const vector
<pair
<int, vector
<pair
<int, iplist_t
> > > >& in
) {
1088 for(const auto& rule
: in
) {
1089 const auto& netmasks
=rule
.second
[0].second
;
1090 const auto& destinations
=rule
.second
[1].second
;
1091 for(const auto& nmpair
: netmasks
) {
1092 Netmask
nm(nmpair
.second
);
1093 if(nm
.match(s_lua_record_ctx
->bestwho
)) {
1094 if (destinations
.empty()) {
1095 throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm
.toString() + ")");
1097 return destinations
[dns_random(destinations
.size())].second
;
1101 return std::string();
1104 lua
.writeFunction("all", [](const vector
< pair
<int,string
> >& ips
) {
1105 vector
<string
> result
;
1106 result
.reserve(ips
.size());
1108 for(const auto& ip
: ips
) {
1109 result
.emplace_back(ip
.second
);
1111 if(result
.empty()) {
1112 throw std::invalid_argument("The IP list cannot be empty");
1117 lua
.writeFunction("dblookup", [](const string
& record
, const string
& type
) {
1122 rec
= DNSName(record
);
1124 if (qtype
.getCode() == 0) {
1125 throw std::invalid_argument("unknown type");
1128 catch (const std::exception
& e
) {
1129 g_log
<< Logger::Error
<< "DB lookup cannot be performed, the name (" << record
<< ") or type (" << type
<< ") is malformed: " << e
.what() << endl
;
1135 if (!getAuth(rec
, qtype
, &soaData
)) {
1139 vector
<DNSZoneRecord
> drs
= lookup(rec
, qtype
, soaData
.domain_id
);
1140 for (const auto& drec
: drs
) {
1141 ret
.push_back(drec
.dr
.getContent()->getZoneRepresentation());
1144 catch (std::exception
& e
) {
1145 g_log
<< Logger::Error
<< "Failed to do DB lookup for " << rec
<< "/" << qtype
<< ": " << e
.what() << endl
;
1150 lua
.writeFunction("include", [&lua
](string record
) {
1153 rec
= DNSName(record
) + s_lua_record_ctx
->zone
;
1154 } catch (const std::exception
&e
){
1155 g_log
<<Logger::Error
<<"Included record cannot be loaded, the name ("<<record
<<") is malformed: "<<e
.what()<<endl
;
1159 vector
<DNSZoneRecord
> drs
= lookup(rec
, QType::LUA
, s_lua_record_ctx
->zoneid
);
1160 for(const auto& dr
: drs
) {
1161 auto lr
= getRR
<LUARecordContent
>(dr
.dr
);
1162 lua
.executeCode(lr
->getCode());
1165 catch(std::exception
& e
) {
1166 g_log
<<Logger::Error
<<"Failed to load include record for LUArecord "<<rec
<<": "<<e
.what()<<endl
;
1171 std::vector
<shared_ptr
<DNSRecordContent
>> luaSynth(const std::string
& code
, const DNSName
& query
, const DNSName
& zone
, int zoneid
, const DNSPacket
& dnsp
, uint16_t qtype
, unique_ptr
<AuthLua4
>& LUA
)
1173 if(!LUA
|| // we don't have a Lua state yet
1174 !g_LuaRecordSharedState
) { // or we want a new one even if we had one
1175 LUA
= make_unique
<AuthLua4
>();
1176 setupLuaRecords(*LUA
->getLua());
1179 std::vector
<shared_ptr
<DNSRecordContent
>> ret
;
1181 LuaContext
& lua
= *LUA
->getLua();
1183 s_lua_record_ctx
= std::make_unique
<lua_record_ctx_t
>();
1184 s_lua_record_ctx
->qname
= query
;
1185 s_lua_record_ctx
->zone
= zone
;
1186 s_lua_record_ctx
->zoneid
= zoneid
;
1188 lua
.writeVariable("qname", query
);
1189 lua
.writeVariable("zone", zone
);
1190 lua
.writeVariable("zoneid", zoneid
);
1191 lua
.writeVariable("who", dnsp
.getInnerRemote());
1192 lua
.writeVariable("localwho", dnsp
.getLocal());
1193 lua
.writeVariable("dh", (dnsheader
*)&dnsp
.d
);
1194 lua
.writeVariable("dnssecOK", dnsp
.d_dnssecOk
);
1195 lua
.writeVariable("tcp", dnsp
.d_tcp
);
1196 lua
.writeVariable("ednsPKTSize", dnsp
.d_ednsRawPacketSizeLimit
);
1197 if(dnsp
.hasEDNSSubnet()) {
1198 lua
.writeVariable("ecswho", dnsp
.getRealRemote());
1199 s_lua_record_ctx
->bestwho
= dnsp
.getRealRemote().getNetwork();
1202 lua
.writeVariable("ecswho", nullptr);
1203 s_lua_record_ctx
->bestwho
= dnsp
.getInnerRemote();
1205 lua
.writeVariable("bestwho", s_lua_record_ctx
->bestwho
);
1209 if(!code
.empty() && code
[0]!=';')
1210 actual
= "return " + code
;
1212 actual
= code
.substr(1);
1214 auto content
=lua
.executeCode
<boost::variant
<string
, vector
<pair
<int, string
> > > >(actual
);
1216 vector
<string
> contents
;
1217 if(auto str
= boost::get
<string
>(&content
))
1218 contents
.push_back(*str
);
1220 for(const auto& c
: boost::get
<vector
<pair
<int,string
>>>(content
))
1221 contents
.push_back(c
.second
);
1223 for(const auto& content_it
: contents
) {
1224 if(qtype
==QType::TXT
)
1225 ret
.push_back(DNSRecordContent::make(qtype
, QClass::IN
, '"' + content_it
+ '"'));
1227 ret
.push_back(DNSRecordContent::make(qtype
, QClass::IN
, content_it
));
1229 } catch(std::exception
&e
) {
1230 g_log
<< Logger::Info
<< "Lua record ("<<query
<<"|"<<QType(qtype
).toString()<<") reported: " << e
.what();
1232 std::rethrow_if_nested(e
);
1234 } catch(const std::exception
& ne
) {
1235 g_log
<< ": " << ne
.what() << std::endl
;
1237 catch(const PDNSException
& ne
) {
1238 g_log
<< ": " << ne
.reason
<< std::endl
;