3 #include <boost/format.hpp>
4 #include <boost/uuid/string_generator.hpp>
11 #include "ext/luawrapper/include/LuaContext.hpp"
13 #include "lua-auth4.hh"
15 #include "minicurl.hh"
16 #include "ueberbackend.hh"
17 #include "dns_random.hh"
18 #include "auth-main.hh"
19 #include "../modules/geoipbackend/geoipinterface.hh" // only for the enum
22 block AXFR unless TSIG, or override
26 check the wildcard 'no cache' stuff, we may get it wrong
28 ponder ECS scopemask setting
30 ponder netmask tree from file for huge number of netmasks
32 unify ifurlup/ifportup
33 add attribute for certificate check
34 add list of current monitors
37 pool of UeberBackends?
42 extern int g_luaRecordExecLimit
;
44 using iplist_t
= vector
<pair
<int, string
> >;
45 using wiplist_t
= std::unordered_map
<int, string
>;
46 using ipunitlist_t
= vector
<pair
<int, iplist_t
> >;
47 using opts_t
= std::unordered_map
<string
,string
>;
57 bool operator<(const CheckDesc
& rhs
) const
59 std::map
<string
,string
> oopts
, rhsoopts
;
60 for(const auto& m
: opts
)
61 oopts
[m
.first
]=m
.second
;
62 for(const auto& m
: rhs
.opts
)
63 rhsoopts
[m
.first
]=m
.second
;
65 return std::tuple(rem
, url
, oopts
) <
66 std::tuple(rhs
.rem
, rhs
.url
, rhsoopts
);
71 CheckState(time_t _lastAccess
): lastAccess(_lastAccess
) {}
73 std::atomic
<bool> status
{false};
75 std::atomic
<bool> first
{true};
76 /* last time the status was accessed */
77 std::atomic
<time_t> lastAccess
{0};
83 d_checkerThreadStarted
.clear();
85 ~IsUpOracle() = default;
86 bool isUp(const ComboAddress
& remote
, const opts_t
& opts
);
87 bool isUp(const ComboAddress
& remote
, const std::string
& url
, const opts_t
& opts
);
88 bool isUp(const CheckDesc
& cd
);
91 void checkURL(const CheckDesc
& cd
, const bool status
, const bool first
= false)
96 if (cd
.opts
.count("timeout")) {
97 timeout
= std::atoi(cd
.opts
.at("timeout").c_str());
99 string useragent
= productName();
100 if (cd
.opts
.count("useragent")) {
101 useragent
= cd
.opts
.at("useragent");
103 size_t byteslimit
= 0;
104 if (cd
.opts
.count("byteslimit")) {
105 byteslimit
= static_cast<size_t>(std::atoi(cd
.opts
.at("byteslimit").c_str()));
107 MiniCurl
mc(useragent
);
110 const ComboAddress
* rem
= nullptr;
111 if(cd
.rem
.sin4
.sin_family
!= AF_UNSPEC
) {
113 remstring
= rem
->toString();
115 remstring
= "[externally checked IP]";
118 if (cd
.opts
.count("source")) {
119 ComboAddress
src(cd
.opts
.at("source"));
120 content
=mc
.getURL(cd
.url
, rem
, &src
, timeout
, false, false, byteslimit
);
123 content
=mc
.getURL(cd
.url
, rem
, nullptr, timeout
, false, false, byteslimit
);
125 if (cd
.opts
.count("stringmatch") && content
.find(cd
.opts
.at("stringmatch")) == string::npos
) {
126 throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % cd
.opts
.at("stringmatch")));
130 g_log
<<Logger::Info
<<"LUA record monitoring declaring "<<remstring
<<" UP for URL "<<cd
.url
<<"!"<<endl
;
134 catch(std::exception
& ne
) {
136 g_log
<<Logger::Info
<<"LUA record monitoring declaring "<<remstring
<<" DOWN for URL "<<cd
.url
<<", error: "<<ne
.what()<<endl
;
140 void checkTCP(const CheckDesc
& cd
, const bool status
, const bool first
= false) {
143 if (cd
.opts
.count("timeout")) {
144 timeout
= std::atoi(cd
.opts
.at("timeout").c_str());
146 Socket
s(cd
.rem
.sin4
.sin_family
, SOCK_STREAM
);
149 if (cd
.opts
.count("source")) {
150 src
= ComboAddress(cd
.opts
.at("source"));
153 s
.connect(cd
.rem
, timeout
);
155 g_log
<<Logger::Info
<<"Lua record monitoring declaring TCP/IP "<<cd
.rem
.toStringWithPort()<<" ";
156 if(cd
.opts
.count("source"))
157 g_log
<<"(source "<<src
.toString()<<") ";
162 catch (const NetworkError
& ne
) {
163 if(status
|| first
) {
164 g_log
<<Logger::Info
<<"Lua record monitoring declaring TCP/IP "<<cd
.rem
.toStringWithPort()<<" DOWN: "<<ne
.what()<<endl
;
173 std::chrono::system_clock::time_point checkStart
= std::chrono::system_clock::now();
174 std::vector
<std::future
<void>> results
;
175 std::vector
<CheckDesc
> toDelete
;
177 // make sure there's no insertion
178 auto statuses
= d_statuses
.read_lock();
179 for (auto& it
: *statuses
) {
180 auto& desc
= it
.first
;
181 auto& state
= it
.second
;
183 if (desc
.url
.empty()) { // TCP
184 results
.push_back(std::async(std::launch::async
, &IsUpOracle::checkTCP
, this, desc
, state
->status
.load(), state
->first
.load()));
186 results
.push_back(std::async(std::launch::async
, &IsUpOracle::checkURL
, this, desc
, state
->status
.load(), state
->first
.load()));
188 if (std::chrono::system_clock::from_time_t(state
->lastAccess
) < (checkStart
- std::chrono::seconds(g_luaHealthChecksExpireDelay
))) {
189 toDelete
.push_back(desc
);
193 // we can release the lock as nothing will be deleted
194 for (auto& future
: results
) {
197 if (!toDelete
.empty()) {
198 auto statuses
= d_statuses
.write_lock();
199 for (auto& it
: toDelete
) {
203 std::this_thread::sleep_until(checkStart
+ std::chrono::seconds(g_luaHealthChecksInterval
));
207 typedef map
<CheckDesc
, std::unique_ptr
<CheckState
>> statuses_t
;
208 SharedLockGuarded
<statuses_t
> d_statuses
;
210 std::unique_ptr
<std::thread
> d_checkerThread
;
211 std::atomic_flag d_checkerThreadStarted
;
213 void setStatus(const CheckDesc
& cd
, bool status
)
215 auto statuses
= d_statuses
.write_lock();
216 auto& state
= (*statuses
)[cd
];
217 state
->status
= status
;
219 state
->first
= false;
223 void setDown(const ComboAddress
& rem
, const std::string
& url
=std::string(), const opts_t
& opts
= opts_t())
225 CheckDesc cd
{rem
, url
, opts
};
226 setStatus(cd
, false);
229 void setUp(const ComboAddress
& rem
, const std::string
& url
=std::string(), const opts_t
& opts
= opts_t())
231 CheckDesc cd
{rem
, url
, opts
};
236 void setDown(const CheckDesc
& cd
)
238 setStatus(cd
, false);
241 void setUp(const CheckDesc
& cd
)
247 bool IsUpOracle::isUp(const CheckDesc
& cd
)
249 if (!d_checkerThreadStarted
.test_and_set()) {
250 d_checkerThread
= std::make_unique
<std::thread
>([this] { return checkThread(); });
252 time_t now
= time(nullptr);
254 auto statuses
= d_statuses
.read_lock();
255 auto iter
= statuses
->find(cd
);
256 if (iter
!= statuses
->end()) {
257 iter
->second
->lastAccess
= now
;
258 return iter
->second
->status
;
261 // try to parse options so we don't insert any malformed content
262 if (cd
.opts
.count("source")) {
263 ComboAddress
src(cd
.opts
.at("source"));
266 auto statuses
= d_statuses
.write_lock();
267 // Make sure we don't insert new entry twice now we have the lock
268 if (statuses
->find(cd
) == statuses
->end()) {
269 (*statuses
)[cd
] = std::make_unique
<CheckState
>(now
);
275 bool IsUpOracle::isUp(const ComboAddress
& remote
, const opts_t
& opts
)
277 CheckDesc cd
{remote
, "", opts
};
281 bool IsUpOracle::isUp(const ComboAddress
& remote
, const std::string
& url
, const opts_t
& opts
)
283 CheckDesc cd
{remote
, url
, opts
};
289 template<typename T
, typename C
>
290 bool doCompare(const T
& var
, const std::string
& res
, const C
& cmp
)
292 if(auto country
= boost::get
<string
>(&var
))
293 return cmp(*country
, res
);
295 auto countries
=boost::get
<vector
<pair
<int,string
> > >(&var
);
296 for(const auto& country
: *countries
) {
297 if(cmp(country
.second
, res
))
304 static std::string
getGeo(const std::string
& ip
, GeoIPInterface::GeoIPQueryAttribute qa
)
306 static bool initialized
;
307 extern std::function
<std::string(const std::string
& ip
, int)> g_getGeo
;
310 g_log
<<Logger::Error
<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl
;
316 return g_getGeo(ip
, (int)qa
);
319 template <typename T
>
320 static T
pickRandom(const vector
<T
>& items
)
323 throw std::invalid_argument("The items list cannot be empty");
325 return items
[dns_random(items
.size())];
328 template <typename T
>
329 static T
pickHashed(const ComboAddress
& who
, const vector
<T
>& items
)
332 throw std::invalid_argument("The items list cannot be empty");
334 ComboAddress::addressOnlyHash aoh
;
335 return items
[aoh(who
) % items
.size()];
338 template <typename T
>
339 static T
pickWeightedRandom(const vector
< pair
<int, T
> >& items
)
342 throw std::invalid_argument("The items list cannot be empty");
345 vector
< pair
<int, T
> > pick
;
346 pick
.reserve(items
.size());
348 for(auto& i
: items
) {
350 pick
.emplace_back(sum
, i
.second
);
354 throw std::invalid_argument("The sum of items cannot be zero");
357 int r
= dns_random(sum
);
358 auto p
= upper_bound(pick
.begin(), pick
.end(), r
, [](int rarg
, const typename
decltype(pick
)::value_type
& a
) { return rarg
< a
.first
; });
362 template <typename T
>
363 static T
pickWeightedHashed(const ComboAddress
& bestwho
, const vector
< pair
<int, T
> >& items
)
366 throw std::invalid_argument("The items list cannot be empty");
369 vector
< pair
<int, T
> > pick
;
370 pick
.reserve(items
.size());
372 for(auto& i
: items
) {
374 pick
.push_back({sum
, i
.second
});
378 throw std::invalid_argument("The sum of items cannot be zero");
381 ComboAddress::addressOnlyHash aoh
;
382 int r
= aoh(bestwho
) % sum
;
383 auto p
= upper_bound(pick
.begin(), pick
.end(), r
, [](int rarg
, const typename
decltype(pick
)::value_type
& a
) { return rarg
< a
.first
; });
387 template <typename T
>
388 static T
pickWeightedNameHashed(const DNSName
& dnsname
, vector
< pair
<int, T
> >& items
)
391 throw std::invalid_argument("The items list cannot be empty");
394 vector
< pair
<int, T
> > pick
;
395 pick
.reserve(items
.size());
397 for(auto& i
: items
) {
399 pick
.push_back({sum
, i
.second
});
403 throw std::invalid_argument("The sum of items cannot be zero");
406 size_t r
= dnsname
.hash() % sum
;
407 auto p
= upper_bound(pick
.begin(), pick
.end(), r
, [](int rarg
, const typename
decltype(pick
)::value_type
& a
) { return rarg
< a
.first
; });
411 template <typename T
>
412 static vector
<T
> pickRandomSample(int n
, const vector
<T
>& items
)
415 throw std::invalid_argument("The items list cannot be empty");
419 pick
.reserve(items
.size());
421 for(auto& item
: items
) {
422 pick
.push_back(item
);
425 int count
= std::min(std::max
<size_t>(0, n
), items
.size());
431 std::shuffle(pick
.begin(), pick
.end(), pdns::dns_random_engine());
433 vector
<T
> result
= {pick
.begin(), pick
.begin() + count
};
437 static bool getLatLon(const std::string
& ip
, double& lat
, double& lon
)
439 string inp
= getGeo(ip
, GeoIPInterface::Location
);
442 lat
=atof(inp
.c_str());
443 auto pos
=inp
.find(' ');
444 if(pos
!= string::npos
)
445 lon
=atof(inp
.c_str() + pos
);
449 static bool getLatLon(const std::string
& ip
, string
& loc
)
451 int latdeg
, latmin
, londeg
, lonmin
;
452 double latsec
, lonsec
;
453 char lathem
='X', lonhem
='X';
455 double lat
= 0, lon
= 0;
456 if(!getLatLon(ip
, lat
, lon
))
476 latmin
= (lat
- latdeg
)*60.0;
477 latsec
= (((lat
- latdeg
)*60.0) - latmin
)*60.0;
480 lonmin
= (lon
- londeg
)*60.0;
481 lonsec
= (((lon
- londeg
)*60.0) - lonmin
)*60.0;
483 // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
485 boost::format
fmt("%d %d %d %c %d %d %d %c 0.00m 1.00m 10000.00m 10.00m");
487 loc
= (fmt
% latdeg
% latmin
% latsec
% lathem
% londeg
% lonmin
% lonsec
% lonhem
).str();
491 static ComboAddress
pickclosest(const ComboAddress
& bestwho
, const vector
<ComboAddress
>& wips
)
494 throw std::invalid_argument("The IP list cannot be empty");
496 map
<double, vector
<ComboAddress
> > ranked
;
497 double wlat
=0, wlon
=0;
498 getLatLon(bestwho
.toString(), wlat
, wlon
);
499 // cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
501 for(const auto& c
: wips
) {
503 getLatLon(c
.toString(), lat
, lon
);
504 // cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
505 double latdiff
= wlat
-lat
;
506 double londiff
= wlon
-lon
;
508 londiff
= 360 - londiff
;
509 double dist2
=latdiff
*latdiff
+ londiff
*londiff
;
510 // cout<<" distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
511 ranked
[dist2
].push_back(c
);
513 return ranked
.begin()->second
[dns_random(ranked
.begin()->second
.size())];
516 static std::vector
<DNSZoneRecord
> lookup(const DNSName
& name
, uint16_t qtype
, int zoneid
)
518 static LockGuarded
<UeberBackend
> s_ub
;
521 vector
<DNSZoneRecord
> ret
;
523 auto ub
= s_ub
.lock();
524 ub
->lookup(QType(qtype
), name
, zoneid
);
525 while (ub
->get(dr
)) {
532 static bool getAuth(const DNSName
& name
, uint16_t qtype
, SOAData
* soaData
)
534 static LockGuarded
<UeberBackend
> s_ub
;
537 auto ueback
= s_ub
.lock();
538 return ueback
->getAuth(name
, qtype
, soaData
);
542 static std::string
getOptionValue(const boost::optional
<std::unordered_map
<string
, string
>>& options
, const std::string
&name
, const std::string
&defaultValue
)
544 string selector
=defaultValue
;
546 if(options
->count(name
))
547 selector
=options
->find(name
)->second
;
552 static vector
<ComboAddress
> useSelector(const std::string
&selector
, const ComboAddress
& bestwho
, const vector
<ComboAddress
>& candidates
)
554 vector
<ComboAddress
> ret
;
558 else if(selector
=="random")
559 ret
.emplace_back(pickRandom
<ComboAddress
>(candidates
));
560 else if(selector
=="pickclosest")
561 ret
.emplace_back(pickclosest(bestwho
, candidates
));
562 else if(selector
=="hashed")
563 ret
.emplace_back(pickHashed
<ComboAddress
>(bestwho
, candidates
));
565 g_log
<<Logger::Warning
<<"LUA Record called with unknown selector '"<<selector
<<"'"<<endl
;
566 ret
.emplace_back(pickRandom
<ComboAddress
>(candidates
));
572 static vector
<string
> convComboAddressListToString(const vector
<ComboAddress
>& items
)
574 vector
<string
> result
;
575 result
.reserve(items
.size());
577 for (const auto& item
: items
) {
578 result
.emplace_back(item
.toString());
584 static vector
<ComboAddress
> convComboAddressList(const iplist_t
& items
, uint16_t port
=0)
586 vector
<ComboAddress
> result
;
587 result
.reserve(items
.size());
589 for(const auto& item
: items
) {
590 result
.emplace_back(ComboAddress(item
.second
, port
));
597 * Reads and unify single or multiple sets of ips :
598 * - {'192.0.2.1', '192.0.2.2'}
599 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
602 static vector
<vector
<ComboAddress
>> convMultiComboAddressList(const boost::variant
<iplist_t
, ipunitlist_t
>& items
, uint16_t port
= 0)
604 vector
<vector
<ComboAddress
>> candidates
;
606 if(auto simple
= boost::get
<iplist_t
>(&items
)) {
607 vector
<ComboAddress
> unit
= convComboAddressList(*simple
, port
);
608 candidates
.push_back(unit
);
610 auto units
= boost::get
<ipunitlist_t
>(items
);
611 for(const auto& u
: units
) {
612 vector
<ComboAddress
> unit
= convComboAddressList(u
.second
, port
);
613 candidates
.push_back(unit
);
619 static vector
<string
> convStringList(const iplist_t
& items
)
621 vector
<string
> result
;
622 result
.reserve(items
.size());
624 for(const auto& item
: items
) {
625 result
.emplace_back(item
.second
);
631 static vector
< pair
<int, string
> > convIntStringPairList(const std::unordered_map
<int, wiplist_t
>& items
)
633 vector
<pair
<int,string
> > result
;
634 result
.reserve(items
.size());
636 for(const auto& item
: items
) {
637 result
.emplace_back(atoi(item
.second
.at(1).c_str()), item
.second
.at(2));
643 bool g_LuaRecordSharedState
;
645 typedef struct AuthLuaRecordContext
647 ComboAddress bestwho
;
653 static thread_local unique_ptr
<lua_record_ctx_t
> s_lua_record_ctx
;
656 * Holds computed hashes for a given entry
658 struct EntryHashesHolder
660 std::atomic
<size_t> weight
;
662 SharedLockGuarded
<std::vector
<unsigned int>> hashes
;
663 std::atomic
<time_t> lastUsed
;
665 EntryHashesHolder(size_t weight_
, std::string entry_
, time_t lastUsed_
= time(nullptr)): weight(weight_
), entry(std::move(entry_
)), lastUsed(lastUsed_
) {
668 bool hashesComputed() {
669 return weight
== hashes
.read_lock()->size();
672 auto locked
= hashes
.write_lock();
674 locked
->reserve(weight
);
676 while (count
< weight
) {
677 auto value
= boost::str(boost::format("%s-%d") % entry
% count
);
678 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
679 auto whash
= burtle(reinterpret_cast<const unsigned char*>(value
.data()), value
.size(), 0);
680 locked
->push_back(whash
);
683 std::sort(locked
->begin(), locked
->end());
687 using zone_hashes_key_t
= std::tuple
<int, std::string
, std::string
>;
689 static SharedLockGuarded
<std::map
<
690 zone_hashes_key_t
, // zoneid qname entry
691 std::shared_ptr
<EntryHashesHolder
> // entry w/ corresponding hashes
695 static std::atomic
<time_t> s_lastConsistentHashesCleanup
= 0;
698 * every ~g_luaConsistentHashesCleanupInterval, do a cleanup to delete entries that haven't been used in the last g_luaConsistentHashesExpireDelay
700 static void cleanZoneHashes()
702 auto now
= time(nullptr);
703 if (s_lastConsistentHashesCleanup
> (now
- g_luaConsistentHashesCleanupInterval
)) {
706 s_lastConsistentHashesCleanup
= now
;
707 std::vector
<zone_hashes_key_t
> toDelete
{};
709 auto locked
= s_zone_hashes
.read_lock();
710 auto someTimeAgo
= now
- g_luaConsistentHashesExpireDelay
;
712 for (const auto& [key
, entry
]: *locked
) {
713 if (entry
->lastUsed
> someTimeAgo
) {
714 toDelete
.push_back(key
);
718 if (!toDelete
.empty()) {
719 auto wlocked
= s_zone_hashes
.write_lock();
720 for (const auto& key
: toDelete
) {
726 static std::vector
<std::shared_ptr
<EntryHashesHolder
>> getCHashedEntries(const int zoneId
, const std::string
& queryName
, const std::vector
<std::pair
<int, std::string
>>& items
)
728 std::vector
<std::shared_ptr
<EntryHashesHolder
>> result
{};
729 std::map
<zone_hashes_key_t
, std::shared_ptr
<EntryHashesHolder
>> newEntries
{};
732 time_t now
= time(nullptr);
733 auto locked
= s_zone_hashes
.read_lock();
735 for (const auto& [weight
, entry
]: items
) {
736 auto key
= std::make_tuple(zoneId
, queryName
, entry
);
737 if (locked
->count(key
) == 0) {
738 newEntries
[key
] = std::make_shared
<EntryHashesHolder
>(weight
, entry
, now
);
740 locked
->at(key
)->weight
= weight
;
741 locked
->at(key
)->lastUsed
= now
;
742 result
.push_back(locked
->at(key
));
746 if (!newEntries
.empty()) {
747 auto wlocked
= s_zone_hashes
.write_lock();
749 for (auto& [key
, entry
]: newEntries
) {
750 result
.push_back(entry
);
751 (*wlocked
)[key
] = std::move(entry
);
758 static std::string
pickConsistentWeightedHashed(const ComboAddress
& bestwho
, const std::vector
<std::pair
<int, std::string
>>& items
)
760 const auto& zoneId
= s_lua_record_ctx
->zoneid
;
761 const auto queryName
= s_lua_record_ctx
->qname
.toString();
762 unsigned int sel
= std::numeric_limits
<unsigned int>::max();
763 unsigned int min
= std::numeric_limits
<unsigned int>::max();
765 boost::optional
<std::string
> ret
;
766 boost::optional
<std::string
> first
;
770 auto entries
= getCHashedEntries(zoneId
, queryName
, items
);
772 ComboAddress::addressOnlyHash addrOnlyHash
;
773 auto qhash
= addrOnlyHash(bestwho
);
774 for (const auto& entry
: entries
) {
775 if (!entry
->hashesComputed()) {
779 const auto hashes
= entry
->hashes
.read_lock();
780 if (!hashes
->empty()) {
781 if (min
> *(hashes
->begin())) {
782 min
= *(hashes
->begin());
783 first
= entry
->entry
;
786 auto hash_it
= std::lower_bound(hashes
->begin(), hashes
->end(), qhash
);
787 if (hash_it
!= hashes
->end()) {
788 if (*hash_it
< sel
) {
796 if (ret
!= boost::none
) {
799 if (first
!= boost::none
) {
805 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)
807 vector
<vector
<ComboAddress
> > candidates
;
812 candidates
= convMultiComboAddressList(ips
, port
);
814 for(const auto& unit
: candidates
) {
815 vector
<ComboAddress
> available
;
816 for(const auto& c
: unit
) {
817 if (upcheckf(c
, opts
)) {
818 available
.push_back(c
);
821 if(!available
.empty()) {
822 vector
<ComboAddress
> res
= useSelector(getOptionValue(options
, "selector", "random"), s_lua_record_ctx
->bestwho
, available
);
823 return convComboAddressListToString(res
);
827 // All units down, apply backupSelector on all candidates
828 vector
<ComboAddress
> ret
{};
829 for(const auto& unit
: candidates
) {
830 ret
.insert(ret
.end(), unit
.begin(), unit
.end());
833 vector
<ComboAddress
> res
= useSelector(getOptionValue(options
, "backupSelector", "random"), s_lua_record_ctx
->bestwho
, ret
);
834 return convComboAddressListToString(res
);
837 static void setupLuaRecords(LuaContext
& lua
) // NOLINT(readability-function-cognitive-complexity)
839 lua
.writeFunction("latlon", []() {
840 double lat
= 0, lon
= 0;
841 getLatLon(s_lua_record_ctx
->bestwho
.toString(), lat
, lon
);
842 return std::to_string(lat
)+" "+std::to_string(lon
);
844 lua
.writeFunction("latlonloc", []() {
846 getLatLon(s_lua_record_ctx
->bestwho
.toString(), loc
);
849 lua
.writeFunction("closestMagic", []() {
850 vector
<ComboAddress
> candidates
;
851 // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
852 for(auto l
: s_lua_record_ctx
->qname
.getRawLabels()) {
853 boost::replace_all(l
, "-", ".");
855 candidates
.emplace_back(l
);
856 } catch (const PDNSException
& e
) {
857 // no need to continue as we most likely reached the end of the ip list
861 return pickclosest(s_lua_record_ctx
->bestwho
, candidates
).toString();
863 lua
.writeFunction("latlonMagic", [](){
864 auto labels
= s_lua_record_ctx
->qname
.getRawLabels();
866 return std::string("unknown");
867 double lat
= 0, lon
= 0;
868 getLatLon(labels
[3]+"."+labels
[2]+"."+labels
[1]+"."+labels
[0], lat
, lon
);
869 return std::to_string(lat
)+" "+std::to_string(lon
);
873 lua
.writeFunction("createReverse", [](string format
, boost::optional
<std::unordered_map
<string
,string
>> e
){
875 auto labels
= s_lua_record_ctx
->qname
.getRawLabels();
877 return std::string("unknown");
879 vector
<ComboAddress
> candidates
;
881 // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
882 // e["1.2.3.4"]="bert.powerdns.com" then provides an exception
884 ComboAddress
req(labels
[3]+"."+labels
[2]+"."+labels
[1]+"."+labels
[0], 0);
885 const auto& uom
= *e
;
886 for(const auto& c
: uom
)
887 if(ComboAddress(c
.first
, 0) == req
)
890 boost::format
fmt(format
);
891 fmt
.exceptions( boost::io::all_error_bits
^ ( boost::io::too_many_args_bit
| boost::io::too_few_args_bit
) );
892 fmt
% labels
[3] % labels
[2] % labels
[1] % labels
[0];
894 fmt
% (labels
[3]+"-"+labels
[2]+"-"+labels
[1]+"-"+labels
[0]);
896 boost::format
fmt2("%02x%02x%02x%02x");
897 for(int i
=3; i
>=0; --i
)
898 fmt2
% atoi(labels
[i
].c_str());
904 catch(std::exception
& ex
) {
905 g_log
<<Logger::Error
<<"error: "<<ex
.what()<<endl
;
907 return std::string("error");
909 lua
.writeFunction("createForward", []() {
910 static string
allZerosIP("0.0.0.0");
911 DNSName rel
=s_lua_record_ctx
->qname
.makeRelative(s_lua_record_ctx
->zone
);
912 // parts is something like ["1", "2", "3", "4", "static"] or
913 // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
914 auto parts
= rel
.getRawLabels();
915 // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
916 if(parts
.size()>=4) {
918 ComboAddress
ca(parts
[0]+"."+parts
[1]+"."+parts
[2]+"."+parts
[3]);
919 return ca
.toString();
920 } catch (const PDNSException
&e
) {
923 } else if (!parts
.empty()) {
924 auto& input
= parts
.at(0);
926 // allow a word without - in front, as long as it does not contain anything that could be a number
927 size_t nonhexprefix
= strcspn(input
.c_str(), "0123456789abcdefABCDEF");
928 if (nonhexprefix
> 0) {
929 input
= input
.substr(nonhexprefix
);
932 // either hex string, or 12-13-14-15
933 vector
<string
> ip_parts
;
935 stringtok(ip_parts
, input
, "-");
936 unsigned int x1
, x2
, x3
, x4
;
937 if (ip_parts
.size() >= 4) {
938 // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
940 for (size_t index
=4; index
> 0; index
--) {
941 auto octet
= ip_parts
[ip_parts
.size() - index
];
943 auto octetVal
= std::stol(octet
);
944 if (octetVal
>= 0 && octetVal
<= 255) {
945 ret
+= ip_parts
.at(ip_parts
.size() - index
) + ".";
949 } catch (const std::exception
&e
) {
953 ret
.resize(ret
.size() - 1); // remove trailing dot after last octet
956 if(input
.length() >= 8) {
957 auto last8
= input
.substr(input
.length()-8);
958 if(sscanf(last8
.c_str(), "%02x%02x%02x%02x", &x1
, &x2
, &x3
, &x4
)==4) {
959 return std::to_string(x1
) + "." + std::to_string(x2
) + "." + std::to_string(x3
) + "." + std::to_string(x4
);
966 lua
.writeFunction("createForward6", []() {
967 DNSName rel
=s_lua_record_ctx
->qname
.makeRelative(s_lua_record_ctx
->zone
);
968 auto parts
= rel
.getRawLabels();
969 if(parts
.size()==8) {
971 for(int i
=0; i
<8; ++i
) {
976 ComboAddress
ca(tot
);
977 return ca
.toString();
979 else if(parts
.size()==1) {
980 if (parts
[0].find('-') != std::string::npos
) {
981 boost::replace_all(parts
[0],"-",":");
982 ComboAddress
ca(parts
[0]);
983 return ca
.toString();
985 if (parts
[0].size() >= 32) {
986 auto ippart
= parts
[0].substr(parts
[0].size()-32);
988 ippart
.substr(0, 4) + ":" +
989 ippart
.substr(4, 4) + ":" +
990 ippart
.substr(8, 4) + ":" +
991 ippart
.substr(12, 4) + ":" +
992 ippart
.substr(16, 4) + ":" +
993 ippart
.substr(20, 4) + ":" +
994 ippart
.substr(24, 4) + ":" +
995 ippart
.substr(28, 4);
997 ComboAddress
ca(fulladdress
);
998 return ca
.toString();
1003 return std::string("::");
1005 lua
.writeFunction("createReverse6", [](string format
, boost::optional
<std::unordered_map
<string
,string
>> e
){
1006 vector
<ComboAddress
> candidates
;
1009 auto labels
= s_lua_record_ctx
->qname
.getRawLabels();
1010 if(labels
.size()<32)
1011 return std::string("unknown");
1012 boost::format
fmt(format
);
1013 fmt
.exceptions( boost::io::all_error_bits
^ ( boost::io::too_many_args_bit
| boost::io::too_few_args_bit
) );
1017 vector
<string
> quads
;
1018 for(int i
=0; i
<8; ++i
) {
1022 for(int j
=0; j
<4; ++j
) {
1023 lquad
.append(1, labels
[31-i
*4-j
][0]);
1024 together
+= labels
[31-i
*4-j
][0];
1026 quads
.push_back(lquad
);
1028 ComboAddress
ip6(together
,0);
1032 for(const auto& addr
: addrs
) {
1033 // this makes sure we catch all forms of the address
1034 if(ComboAddress(addr
.first
,0)==ip6
)
1039 string dashed
=ip6
.toString();
1040 boost::replace_all(dashed
, ":", "-");
1042 for(int i
=31; i
>=0; --i
)
1046 for(const auto& lquad
: quads
)
1051 catch(std::exception
& ex
) {
1052 g_log
<<Logger::Error
<<"LUA Record exception: "<<ex
.what()<<endl
;
1054 catch(PDNSException
& ex
) {
1055 g_log
<<Logger::Error
<<"LUA Record exception: "<<ex
.reason
<<endl
;
1057 return std::string("unknown");
1060 lua
.writeFunction("filterForward", [](const string
& address
, NetmaskGroup
& nmg
, boost::optional
<string
> fallback
) -> vector
<string
> {
1061 ComboAddress
ca(address
);
1063 if (nmg
.match(ComboAddress(address
))) {
1067 if (fallback
->empty()) {
1068 // if fallback is an empty string, return an empty array
1075 return {string("0.0.0.0")};
1077 return {string("::")};
1083 * Simplistic test to see if an IP address listens on a certain port
1084 * Will return a single IP address from the set of available IP addresses. If
1085 * no IP address is available, will return a random element of the set of
1086 * addresses supplied for testing.
1088 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
1090 lua
.writeFunction("ifportup", [](int port
, const boost::variant
<iplist_t
, ipunitlist_t
>& ips
, const boost::optional
<std::unordered_map
<string
,string
>> options
) {
1094 if (port
> std::numeric_limits
<uint16_t>::max()) {
1095 port
= std::numeric_limits
<uint16_t>::max();
1098 auto checker
= [](const ComboAddress
& addr
, const opts_t
& opts
) {
1099 return g_up
.isUp(addr
, opts
);
1101 return genericIfUp(ips
, options
, checker
, port
);
1104 lua
.writeFunction("ifurlextup", [](const vector
<pair
<int, opts_t
> >& ipurls
, boost::optional
<opts_t
> options
) {
1105 vector
<ComboAddress
> candidates
;
1110 ComboAddress ca_unspec
;
1111 ca_unspec
.sin4
.sin_family
=AF_UNSPEC
;
1113 // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
1114 for (const auto& [count
, unitmap
] : ipurls
) {
1115 // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
1116 vector
<ComboAddress
> available
;
1118 for (const auto& [ipStr
, url
] : unitmap
) {
1119 // unit: ["192.0.2.1"] = "https://example.com"
1120 ComboAddress
ip(ipStr
);
1121 candidates
.push_back(ip
);
1122 if (g_up
.isUp(ca_unspec
, url
, opts
)) {
1123 available
.push_back(ip
);
1126 if(!available
.empty()) {
1127 vector
<ComboAddress
> res
= useSelector(getOptionValue(options
, "selector", "random"), s_lua_record_ctx
->bestwho
, available
);
1128 return convComboAddressListToString(res
);
1132 // All units down, apply backupSelector on all candidates
1133 vector
<ComboAddress
> res
= useSelector(getOptionValue(options
, "backupSelector", "random"), s_lua_record_ctx
->bestwho
, candidates
);
1134 return convComboAddressListToString(res
);
1137 lua
.writeFunction("ifurlup", [](const std::string
& url
,
1138 const boost::variant
<iplist_t
, ipunitlist_t
>& ips
,
1139 boost::optional
<opts_t
> options
) {
1141 auto checker
= [&url
](const ComboAddress
& addr
, const opts_t
& opts
) {
1142 return g_up
.isUp(addr
, url
, opts
);
1144 return genericIfUp(ips
, options
, checker
);
1147 * Returns a random IP address from the supplied list
1148 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
1150 lua
.writeFunction("pickrandom", [](const iplist_t
& ips
) {
1151 vector
<string
> items
= convStringList(ips
);
1152 return pickRandom
<string
>(items
);
1155 lua
.writeFunction("pickrandomsample", [](int n
, const iplist_t
& ips
) {
1156 vector
<string
> items
= convStringList(ips
);
1157 return pickRandomSample
<string
>(n
, items
);
1160 lua
.writeFunction("pickhashed", [](const iplist_t
& ips
) {
1161 vector
<string
> items
= convStringList(ips
);
1162 return pickHashed
<string
>(s_lua_record_ctx
->bestwho
, items
);
1165 * Returns a random IP address from the supplied list, as weighted by the
1166 * various ``weight`` parameters
1167 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
1169 lua
.writeFunction("pickwrandom", [](std::unordered_map
<int, wiplist_t
> ips
) {
1170 vector
< pair
<int, string
> > items
= convIntStringPairList(ips
);
1171 return pickWeightedRandom
<string
>(items
);
1175 * Based on the hash of `bestwho`, returns an IP address from the list
1176 * supplied, as weighted by the various `weight` parameters
1177 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1179 lua
.writeFunction("pickwhashed", [](std::unordered_map
<int, wiplist_t
> ips
) {
1180 vector
< pair
<int, string
> > items
;
1182 items
.reserve(ips
.size());
1183 for (auto& entry
: ips
) {
1184 items
.emplace_back(atoi(entry
.second
[1].c_str()), entry
.second
[2]);
1187 return pickWeightedHashed
<string
>(s_lua_record_ctx
->bestwho
, items
);
1191 * Based on the hash of the record name, return an IP address from the list
1192 * supplied, as weighted by the various `weight` parameters
1193 * @example picknamehashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1195 lua
.writeFunction("picknamehashed", [](std::unordered_map
<int, wiplist_t
> ips
) {
1196 vector
< pair
<int, string
> > items
;
1198 items
.reserve(ips
.size());
1201 items
.emplace_back(atoi(i
.second
[1].c_str()), i
.second
[2]);
1204 return pickWeightedNameHashed
<string
>(s_lua_record_ctx
->qname
, items
);
1207 * Based on the hash of `bestwho`, returns an IP address from the list
1208 * supplied, as weighted by the various `weight` parameters and distributed consistently
1209 * @example pickchashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
1211 lua
.writeFunction("pickchashed", [](const std::unordered_map
<int, wiplist_t
>& ips
) {
1212 std::vector
<std::pair
<int, std::string
>> items
;
1214 items
.reserve(ips
.size());
1215 for (const auto& entry
: ips
) {
1216 items
.emplace_back(atoi(entry
.second
.at(1).c_str()), entry
.second
.at(2));
1219 return pickConsistentWeightedHashed(s_lua_record_ctx
->bestwho
, items
);
1222 lua
.writeFunction("pickclosest", [](const iplist_t
& ips
) {
1223 vector
<ComboAddress
> conv
= convComboAddressList(ips
);
1225 return pickclosest(s_lua_record_ctx
->bestwho
, conv
).toString();
1229 if (g_luaRecordExecLimit
> 0) {
1230 lua
.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit
));
1233 lua
.writeFunction("report", [](string
/* event */, boost::optional
<string
> /* line */){
1234 throw std::runtime_error("Script took too long");
1237 lua
.writeFunction("geoiplookup", [](const string
&ip
, const GeoIPInterface::GeoIPQueryAttribute attr
) {
1238 return getGeo(ip
, attr
);
1241 typedef const boost::variant
<string
,vector
<pair
<int,string
> > > combovar_t
;
1243 lua
.writeFunction("asnum", [](const combovar_t
& asns
) {
1244 string res
=getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::ASn
);
1245 return doCompare(asns
, res
, [](const std::string
& a
, const std::string
& b
) {
1246 return !strcasecmp(a
.c_str(), b
.c_str());
1249 lua
.writeFunction("continent", [](const combovar_t
& continent
) {
1250 string res
=getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Continent
);
1251 return doCompare(continent
, res
, [](const std::string
& a
, const std::string
& b
) {
1252 return !strcasecmp(a
.c_str(), b
.c_str());
1255 lua
.writeFunction("continentCode", []() {
1256 string
unknown("unknown");
1257 string res
= getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Continent
);
1258 if ( res
== unknown
) {
1259 return std::string("--");
1263 lua
.writeFunction("country", [](const combovar_t
& var
) {
1264 string res
= getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Country2
);
1265 return doCompare(var
, res
, [](const std::string
& a
, const std::string
& b
) {
1266 return !strcasecmp(a
.c_str(), b
.c_str());
1270 lua
.writeFunction("countryCode", []() {
1271 string
unknown("unknown");
1272 string res
= getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Country2
);
1273 if ( res
== unknown
) {
1274 return std::string("--");
1278 lua
.writeFunction("region", [](const combovar_t
& var
) {
1279 string res
= getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Region
);
1280 return doCompare(var
, res
, [](const std::string
& a
, const std::string
& b
) {
1281 return !strcasecmp(a
.c_str(), b
.c_str());
1285 lua
.writeFunction("regionCode", []() {
1286 string
unknown("unknown");
1287 string res
= getGeo(s_lua_record_ctx
->bestwho
.toString(), GeoIPInterface::Region
);
1288 if ( res
== unknown
) {
1289 return std::string("--");
1293 lua
.writeFunction("netmask", [](const iplist_t
& ips
) {
1294 for(const auto& i
:ips
) {
1295 Netmask
nm(i
.second
);
1296 if(nm
.match(s_lua_record_ctx
->bestwho
))
1303 {'192.168.0.0/16', '10.0.0.0/8'},
1304 {'192.168.20.20', '192.168.20.21'}
1307 {'0.0.0.0/0'}, {'192.0.2.1'}
1311 lua
.writeFunction("view", [](const vector
<pair
<int, vector
<pair
<int, iplist_t
> > > >& in
) {
1312 for(const auto& rule
: in
) {
1313 const auto& netmasks
=rule
.second
[0].second
;
1314 const auto& destinations
=rule
.second
[1].second
;
1315 for(const auto& nmpair
: netmasks
) {
1316 Netmask
nm(nmpair
.second
);
1317 if(nm
.match(s_lua_record_ctx
->bestwho
)) {
1318 if (destinations
.empty()) {
1319 throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm
.toString() + ")");
1321 return destinations
[dns_random(destinations
.size())].second
;
1325 return std::string();
1328 lua
.writeFunction("all", [](const vector
< pair
<int,string
> >& ips
) {
1329 vector
<string
> result
;
1330 result
.reserve(ips
.size());
1332 for(const auto& ip
: ips
) {
1333 result
.emplace_back(ip
.second
);
1335 if(result
.empty()) {
1336 throw std::invalid_argument("The IP list cannot be empty");
1341 lua
.writeFunction("dblookup", [](const string
& record
, uint16_t qtype
) {
1345 rec
= DNSName(record
);
1347 catch (const std::exception
& e
) {
1348 g_log
<< Logger::Error
<< "DB lookup cannot be performed, the name (" << record
<< ") is malformed: " << e
.what() << endl
;
1354 if (!getAuth(rec
, qtype
, &soaData
)) {
1358 vector
<DNSZoneRecord
> drs
= lookup(rec
, qtype
, soaData
.domain_id
);
1359 for (const auto& drec
: drs
) {
1360 ret
.push_back(drec
.dr
.getContent()->getZoneRepresentation());
1363 catch (std::exception
& e
) {
1364 g_log
<< Logger::Error
<< "Failed to do DB lookup for " << rec
<< "/" << qtype
<< ": " << e
.what() << endl
;
1369 lua
.writeFunction("include", [&lua
](string record
) {
1372 rec
= DNSName(record
) + s_lua_record_ctx
->zone
;
1373 } catch (const std::exception
&e
){
1374 g_log
<<Logger::Error
<<"Included record cannot be loaded, the name ("<<record
<<") is malformed: "<<e
.what()<<endl
;
1378 vector
<DNSZoneRecord
> drs
= lookup(rec
, QType::LUA
, s_lua_record_ctx
->zoneid
);
1379 for(const auto& dr
: drs
) {
1380 auto lr
= getRR
<LUARecordContent
>(dr
.dr
);
1381 lua
.executeCode(lr
->getCode());
1384 catch(std::exception
& e
) {
1385 g_log
<<Logger::Error
<<"Failed to load include record for LUArecord "<<rec
<<": "<<e
.what()<<endl
;
1390 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
)
1392 if(!LUA
|| // we don't have a Lua state yet
1393 !g_LuaRecordSharedState
) { // or we want a new one even if we had one
1394 LUA
= make_unique
<AuthLua4
>();
1395 setupLuaRecords(*LUA
->getLua());
1398 std::vector
<shared_ptr
<DNSRecordContent
>> ret
;
1400 LuaContext
& lua
= *LUA
->getLua();
1402 s_lua_record_ctx
= std::make_unique
<lua_record_ctx_t
>();
1403 s_lua_record_ctx
->qname
= query
;
1404 s_lua_record_ctx
->zone
= zone
;
1405 s_lua_record_ctx
->zoneid
= zoneid
;
1407 lua
.writeVariable("qname", query
);
1408 lua
.writeVariable("zone", zone
);
1409 lua
.writeVariable("zoneid", zoneid
);
1410 lua
.writeVariable("who", dnsp
.getInnerRemote());
1411 lua
.writeVariable("localwho", dnsp
.getLocal());
1412 lua
.writeVariable("dh", (dnsheader
*)&dnsp
.d
);
1413 lua
.writeVariable("dnssecOK", dnsp
.d_dnssecOk
);
1414 lua
.writeVariable("tcp", dnsp
.d_tcp
);
1415 lua
.writeVariable("ednsPKTSize", dnsp
.d_ednsRawPacketSizeLimit
);
1416 if(dnsp
.hasEDNSSubnet()) {
1417 lua
.writeVariable("ecswho", dnsp
.getRealRemote());
1418 s_lua_record_ctx
->bestwho
= dnsp
.getRealRemote().getNetwork();
1421 lua
.writeVariable("ecswho", nullptr);
1422 s_lua_record_ctx
->bestwho
= dnsp
.getInnerRemote();
1424 lua
.writeVariable("bestwho", s_lua_record_ctx
->bestwho
);
1428 if(!code
.empty() && code
[0]!=';')
1429 actual
= "return " + code
;
1431 actual
= code
.substr(1);
1433 auto content
=lua
.executeCode
<boost::variant
<string
, vector
<pair
<int, string
> > > >(actual
);
1435 vector
<string
> contents
;
1436 if(auto str
= boost::get
<string
>(&content
))
1437 contents
.push_back(*str
);
1439 for(const auto& c
: boost::get
<vector
<pair
<int,string
>>>(content
))
1440 contents
.push_back(c
.second
);
1442 for(const auto& content_it
: contents
) {
1443 if(qtype
==QType::TXT
)
1444 ret
.push_back(DNSRecordContent::make(qtype
, QClass::IN
, '"' + content_it
+ '"'));
1446 ret
.push_back(DNSRecordContent::make(qtype
, QClass::IN
, content_it
));
1448 } catch(std::exception
&e
) {
1449 g_log
<< Logger::Info
<< "Lua record ("<<query
<<"|"<<QType(qtype
).toString()<<") reported: " << e
.what();
1451 std::rethrow_if_nested(e
);
1453 } catch(const std::exception
& ne
) {
1454 g_log
<< ": " << ne
.what() << std::endl
;
1456 catch(const PDNSException
& ne
) {
1457 g_log
<< ": " << ne
.reason
<< std::endl
;