]>
Commit | Line | Data |
---|---|---|
2c010a9f | 1 | #include <thread> |
a6897a16 | 2 | #include <future> |
a6897a16 | 3 | #include <boost/format.hpp> |
4a0d414b | 4 | #include <boost/uuid/string_generator.hpp> |
4de01aaa | 5 | #include <utility> |
60f9d9a2 | 6 | #include <algorithm> |
7 | #include <random> | |
3bf2f94d | 8 | #include "qtype.hh" |
4a0d414b | 9 | #include <tuple> |
192e15fc | 10 | #include "version.hh" |
b7edebf8 | 11 | #include "ext/luawrapper/include/LuaContext.hpp" |
18cb84d3 | 12 | #include "lock.hh" |
b7edebf8 | 13 | #include "lua-auth4.hh" |
b7edebf8 | 14 | #include "sstuff.hh" |
b7edebf8 | 15 | #include "minicurl.hh" |
16 | #include "ueberbackend.hh" | |
45d4e670 | 17 | #include "dns_random.hh" |
8cb70f23 | 18 | #include "auth-main.hh" |
f9423419 | 19 | #include "../modules/geoipbackend/geoipinterface.hh" // only for the enum |
0540d223 | 20 | |
21 | /* to do: | |
25bcfaec | 22 | block AXFR unless TSIG, or override |
69ec43c3 | 23 | |
24 | investigate IPv6 | |
25 | ||
26 | check the wildcard 'no cache' stuff, we may get it wrong | |
27 | ||
28 | ponder ECS scopemask setting | |
29 | ||
30 | ponder netmask tree from file for huge number of netmasks | |
25bcfaec | 31 | |
25512599 | 32 | unify ifurlup/ifportup |
69ec43c3 | 33 | add attribute for certificate check |
0540d223 | 34 | add list of current monitors |
35 | expire them too? | |
c9d40b1f | 36 | |
37 | pool of UeberBackends? | |
1bc56192 CHB |
38 | |
39 | Pool checks ? | |
0540d223 | 40 | */ |
41 | ||
af68014f CHB |
42 | extern int g_luaRecordExecLimit; |
43 | ||
1bc56192 CHB |
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>; | |
48 | ||
b7edebf8 | 49 | class IsUpOracle |
50 | { | |
51 | private: | |
b7edebf8 | 52 | struct CheckDesc |
53 | { | |
54 | ComboAddress rem; | |
55 | string url; | |
56 | opts_t opts; | |
57 | bool operator<(const CheckDesc& rhs) const | |
58 | { | |
25bcfaec | 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; | |
1bc56192 | 64 | |
0b0882f5 RP |
65 | return std::tuple(rem, url, oopts) < |
66 | std::tuple(rhs.rem, rhs.url, rhsoopts); | |
b7edebf8 | 67 | } |
68 | }; | |
a6897a16 CHB |
69 | struct CheckState |
70 | { | |
71 | CheckState(time_t _lastAccess): lastAccess(_lastAccess) {} | |
72 | /* current status */ | |
73 | std::atomic<bool> status{false}; | |
74 | /* first check ? */ | |
75 | std::atomic<bool> first{true}; | |
76 | /* last time the status was accessed */ | |
77 | std::atomic<time_t> lastAccess{0}; | |
78 | }; | |
79 | ||
b7edebf8 | 80 | public: |
d27a2d30 | 81 | IsUpOracle() |
a6897a16 | 82 | { |
d27a2d30 | 83 | d_checkerThreadStarted.clear(); |
a6897a16 | 84 | } |
abb11ca4 | 85 | ~IsUpOracle() = default; |
1bc56192 CHB |
86 | bool isUp(const ComboAddress& remote, const opts_t& opts); |
87 | bool isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts); | |
25bcfaec | 88 | bool isUp(const CheckDesc& cd); |
1bc56192 | 89 | |
b7edebf8 | 90 | private: |
a6897a16 CHB |
91 | void checkURL(const CheckDesc& cd, const bool status, const bool first = false) |
92 | { | |
95d0df69 | 93 | string remstring; |
a6897a16 | 94 | try { |
b69ea3b5 | 95 | int timeout = 2; |
a6897a16 CHB |
96 | if (cd.opts.count("timeout")) { |
97 | timeout = std::atoi(cd.opts.at("timeout").c_str()); | |
98 | } | |
99 | string useragent = productName(); | |
100 | if (cd.opts.count("useragent")) { | |
101 | useragent = cd.opts.at("useragent"); | |
102 | } | |
0e524ab0 CHB |
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())); | |
106 | } | |
a6897a16 | 107 | MiniCurl mc(useragent); |
b7edebf8 | 108 | |
a6897a16 | 109 | string content; |
95d0df69 PD |
110 | const ComboAddress* rem = nullptr; |
111 | if(cd.rem.sin4.sin_family != AF_UNSPEC) { | |
112 | rem = &cd.rem; | |
113 | remstring = rem->toString(); | |
114 | } else { | |
115 | remstring = "[externally checked IP]"; | |
116 | } | |
117 | ||
a6897a16 CHB |
118 | if (cd.opts.count("source")) { |
119 | ComboAddress src(cd.opts.at("source")); | |
0e524ab0 | 120 | content=mc.getURL(cd.url, rem, &src, timeout, false, false, byteslimit); |
a6897a16 CHB |
121 | } |
122 | else { | |
0e524ab0 | 123 | content=mc.getURL(cd.url, rem, nullptr, timeout, false, false, byteslimit); |
a6897a16 CHB |
124 | } |
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"))); | |
127 | } | |
95d0df69 | 128 | |
a6897a16 | 129 | if(!status) { |
95d0df69 | 130 | g_log<<Logger::Info<<"LUA record monitoring declaring "<<remstring<<" UP for URL "<<cd.url<<"!"<<endl; |
a6897a16 CHB |
131 | } |
132 | setUp(cd); | |
133 | } | |
134 | catch(std::exception& ne) { | |
135 | if(status || first) | |
95d0df69 | 136 | g_log<<Logger::Info<<"LUA record monitoring declaring "<<remstring<<" DOWN for URL "<<cd.url<<", error: "<<ne.what()<<endl; |
a6897a16 CHB |
137 | setDown(cd); |
138 | } | |
139 | } | |
140 | void checkTCP(const CheckDesc& cd, const bool status, const bool first = false) { | |
141 | try { | |
b69ea3b5 | 142 | int timeout = 2; |
a6897a16 CHB |
143 | if (cd.opts.count("timeout")) { |
144 | timeout = std::atoi(cd.opts.at("timeout").c_str()); | |
145 | } | |
146 | Socket s(cd.rem.sin4.sin_family, SOCK_STREAM); | |
147 | ComboAddress src; | |
148 | s.setNonBlocking(); | |
149 | if (cd.opts.count("source")) { | |
150 | src = ComboAddress(cd.opts.at("source")); | |
151 | s.bind(src); | |
152 | } | |
153 | s.connect(cd.rem, timeout); | |
154 | if (!status) { | |
e3982a50 | 155 | g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" "; |
a6897a16 CHB |
156 | if(cd.opts.count("source")) |
157 | g_log<<"(source "<<src.toString()<<") "; | |
158 | g_log<<"UP!"<<endl; | |
159 | } | |
160 | setUp(cd); | |
161 | } | |
162 | catch (const NetworkError& ne) { | |
163 | if(status || first) { | |
e3982a50 | 164 | g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" DOWN: "<<ne.what()<<endl; |
a6897a16 CHB |
165 | } |
166 | setDown(cd); | |
167 | } | |
168 | } | |
169 | void checkThread() | |
b7edebf8 | 170 | { |
a6897a16 CHB |
171 | while (true) |
172 | { | |
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; | |
176 | { | |
bbbfdc22 RG |
177 | // make sure there's no insertion |
178 | auto statuses = d_statuses.read_lock(); | |
179 | for (auto& it: *statuses) { | |
a6897a16 CHB |
180 | auto& desc = it.first; |
181 | auto& state = it.second; | |
182 | ||
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())); | |
185 | } else { // URL | |
186 | results.push_back(std::async(std::launch::async, &IsUpOracle::checkURL, this, desc, state->status.load(), state->first.load())); | |
187 | } | |
188 | if (std::chrono::system_clock::from_time_t(state->lastAccess) < (checkStart - std::chrono::seconds(g_luaHealthChecksExpireDelay))) { | |
189 | toDelete.push_back(desc); | |
190 | } | |
191 | } | |
192 | } | |
193 | // we can release the lock as nothing will be deleted | |
194 | for (auto& future: results) { | |
195 | future.wait(); | |
196 | } | |
197 | if (!toDelete.empty()) { | |
bbbfdc22 | 198 | auto statuses = d_statuses.write_lock(); |
a6897a16 | 199 | for (auto& it: toDelete) { |
bbbfdc22 | 200 | statuses->erase(it); |
a6897a16 CHB |
201 | } |
202 | } | |
203 | std::this_thread::sleep_until(checkStart + std::chrono::seconds(g_luaHealthChecksInterval)); | |
204 | } | |
205 | } | |
b7edebf8 | 206 | |
a6897a16 | 207 | typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t; |
bbbfdc22 | 208 | SharedLockGuarded<statuses_t> d_statuses; |
1bc56192 | 209 | |
a6897a16 | 210 | std::unique_ptr<std::thread> d_checkerThread; |
37f625b0 | 211 | std::atomic_flag d_checkerThreadStarted; |
b7edebf8 | 212 | |
1bc56192 | 213 | void setStatus(const CheckDesc& cd, bool status) |
b7edebf8 | 214 | { |
bbbfdc22 RG |
215 | auto statuses = d_statuses.write_lock(); |
216 | auto& state = (*statuses)[cd]; | |
a6897a16 CHB |
217 | state->status = status; |
218 | if (state->first) { | |
219 | state->first = false; | |
220 | } | |
b7edebf8 | 221 | } |
222 | ||
1bc56192 | 223 | void setDown(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t()) |
b7edebf8 | 224 | { |
225 | CheckDesc cd{rem, url, opts}; | |
226 | setStatus(cd, false); | |
227 | } | |
228 | ||
1bc56192 | 229 | void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t()) |
b7edebf8 | 230 | { |
231 | CheckDesc cd{rem, url, opts}; | |
1bc56192 | 232 | |
b7edebf8 | 233 | setStatus(cd, true); |
234 | } | |
235 | ||
236 | void setDown(const CheckDesc& cd) | |
237 | { | |
238 | setStatus(cd, false); | |
239 | } | |
240 | ||
241 | void setUp(const CheckDesc& cd) | |
242 | { | |
243 | setStatus(cd, true); | |
244 | } | |
b7edebf8 | 245 | }; |
246 | ||
25bcfaec | 247 | bool IsUpOracle::isUp(const CheckDesc& cd) |
b7edebf8 | 248 | { |
37f625b0 | 249 | if (!d_checkerThreadStarted.test_and_set()) { |
2bbc9eb0 | 250 | d_checkerThread = std::make_unique<std::thread>([this] { return checkThread(); }); |
b7edebf8 | 251 | } |
a6897a16 CHB |
252 | time_t now = time(nullptr); |
253 | { | |
bbbfdc22 RG |
254 | auto statuses = d_statuses.read_lock(); |
255 | auto iter = statuses->find(cd); | |
256 | if (iter != statuses->end()) { | |
a6897a16 CHB |
257 | iter->second->lastAccess = now; |
258 | return iter->second->status; | |
259 | } | |
260 | } | |
e3982a50 CHB |
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")); | |
264 | } | |
a6897a16 | 265 | { |
bbbfdc22 | 266 | auto statuses = d_statuses.write_lock(); |
a6897a16 | 267 | // Make sure we don't insert new entry twice now we have the lock |
bbbfdc22 | 268 | if (statuses->find(cd) == statuses->end()) { |
2bbc9eb0 | 269 | (*statuses)[cd] = std::make_unique<CheckState>(now); |
a6897a16 CHB |
270 | } |
271 | } | |
272 | return false; | |
25bcfaec | 273 | } |
274 | ||
1bc56192 | 275 | bool IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts) |
25bcfaec | 276 | { |
277 | CheckDesc cd{remote, "", opts}; | |
278 | return isUp(cd); | |
b7edebf8 | 279 | } |
280 | ||
1bc56192 | 281 | bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts) |
b7edebf8 | 282 | { |
283 | CheckDesc cd{remote, url, opts}; | |
a6897a16 | 284 | return isUp(cd); |
b7edebf8 | 285 | } |
286 | ||
7d5bf0bb | 287 | IsUpOracle g_up; |
b7edebf8 | 288 | namespace { |
289 | template<typename T, typename C> | |
290 | bool doCompare(const T& var, const std::string& res, const C& cmp) | |
291 | { | |
1bc56192 | 292 | if(auto country = boost::get<string>(&var)) |
b7edebf8 | 293 | return cmp(*country, res); |
294 | ||
295 | auto countries=boost::get<vector<pair<int,string> > >(&var); | |
296 | for(const auto& country : *countries) { | |
297 | if(cmp(country.second, res)) | |
298 | return true; | |
299 | } | |
300 | return false; | |
301 | } | |
302 | } | |
4a913a28 | 303 | |
050e6877 | 304 | static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa) |
b7edebf8 | 305 | { |
03be9ab5 | 306 | static bool initialized; |
25bcfaec | 307 | extern std::function<std::string(const std::string& ip, int)> g_getGeo; |
924341d2 | 308 | if(!g_getGeo) { |
309 | if(!initialized) { | |
f5f3d7f5 | 310 | g_log<<Logger::Error<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl; |
924341d2 | 311 | initialized=true; |
312 | } | |
313 | return "unknown"; | |
314 | } | |
315 | else | |
25bcfaec | 316 | return g_getGeo(ip, (int)qa); |
4a913a28 | 317 | } |
318 | ||
f52e47eb PG |
319 | template <typename T> |
320 | static T pickRandom(const vector<T>& items) | |
4a913a28 | 321 | { |
60f9d9a2 | 322 | if (items.empty()) { |
323 | throw std::invalid_argument("The items list cannot be empty"); | |
6d7e1fd3 | 324 | } |
60f9d9a2 | 325 | return items[dns_random(items.size())]; |
4a913a28 | 326 | } |
327 | ||
f52e47eb PG |
328 | template <typename T> |
329 | static T pickHashed(const ComboAddress& who, const vector<T>& items) | |
60f9d9a2 | 330 | { |
331 | if (items.empty()) { | |
332 | throw std::invalid_argument("The items list cannot be empty"); | |
333 | } | |
334 | ComboAddress::addressOnlyHash aoh; | |
335 | return items[aoh(who) % items.size()]; | |
336 | } | |
4a913a28 | 337 | |
f52e47eb PG |
338 | template <typename T> |
339 | static T pickWeightedRandom(const vector< pair<int, T> >& items) | |
975f0839 | 340 | { |
60f9d9a2 | 341 | if (items.empty()) { |
342 | throw std::invalid_argument("The items list cannot be empty"); | |
6d7e1fd3 | 343 | } |
975f0839 | 344 | int sum=0; |
f52e47eb | 345 | vector< pair<int, T> > pick; |
60f9d9a2 | 346 | pick.reserve(items.size()); |
347 | ||
348 | for(auto& i : items) { | |
975f0839 | 349 | sum += i.first; |
e32a8d46 | 350 | pick.emplace_back(sum, i.second); |
975f0839 | 351 | } |
bb85386d | 352 | |
60f9d9a2 | 353 | if (sum == 0) { |
f52e47eb | 354 | throw std::invalid_argument("The sum of items cannot be zero"); |
6d7e1fd3 | 355 | } |
bb85386d | 356 | |
60f9d9a2 | 357 | int r = dns_random(sum); |
f52e47eb | 358 | auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; }); |
60f9d9a2 | 359 | return p->second; |
360 | } | |
361 | ||
f52e47eb | 362 | template <typename T> |
4a0d414b | 363 | static T pickWeightedHashed(const ComboAddress& bestwho, const vector< pair<int, T> >& items) |
60f9d9a2 | 364 | { |
365 | if (items.empty()) { | |
366 | throw std::invalid_argument("The items list cannot be empty"); | |
367 | } | |
975f0839 | 368 | int sum=0; |
f52e47eb | 369 | vector< pair<int, T> > pick; |
60f9d9a2 | 370 | pick.reserve(items.size()); |
371 | ||
372 | for(auto& i : items) { | |
975f0839 | 373 | sum += i.first; |
374 | pick.push_back({sum, i.second}); | |
375 | } | |
bb85386d | 376 | |
60f9d9a2 | 377 | if (sum == 0) { |
f52e47eb | 378 | throw std::invalid_argument("The sum of items cannot be zero"); |
c7cd91db | 379 | } |
60f9d9a2 | 380 | |
975f0839 | 381 | ComboAddress::addressOnlyHash aoh; |
382 | int r = aoh(bestwho) % sum; | |
f52e47eb | 383 | auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; }); |
975f0839 | 384 | return p->second; |
385 | } | |
386 | ||
d33b8bf4 BR |
387 | template <typename T> |
388 | static T pickWeightedNameHashed(const DNSName& dnsname, vector< pair<int, T> >& items) | |
389 | { | |
390 | if (items.empty()) { | |
391 | throw std::invalid_argument("The items list cannot be empty"); | |
392 | } | |
dc03f001 | 393 | size_t sum=0; |
d33b8bf4 BR |
394 | vector< pair<int, T> > pick; |
395 | pick.reserve(items.size()); | |
396 | ||
397 | for(auto& i : items) { | |
398 | sum += i.first; | |
399 | pick.push_back({sum, i.second}); | |
400 | } | |
401 | ||
402 | if (sum == 0) { | |
403 | throw std::invalid_argument("The sum of items cannot be zero"); | |
404 | } | |
405 | ||
dc03f001 | 406 | size_t r = dnsname.hash() % sum; |
d33b8bf4 BR |
407 | auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; }); |
408 | return p->second; | |
409 | } | |
410 | ||
f52e47eb | 411 | template <typename T> |
bb85386d | 412 | static vector<T> pickRandomSample(int n, const vector<T>& items) |
60f9d9a2 | 413 | { |
414 | if (items.empty()) { | |
415 | throw std::invalid_argument("The items list cannot be empty"); | |
416 | } | |
bb85386d | 417 | |
f52e47eb | 418 | vector<T> pick; |
60f9d9a2 | 419 | pick.reserve(items.size()); |
bb85386d | 420 | |
60f9d9a2 | 421 | for(auto& item : items) { |
422 | pick.push_back(item); | |
423 | } | |
bb85386d | 424 | |
60f9d9a2 | 425 | int count = std::min(std::max<size_t>(0, n), items.size()); |
426 | ||
427 | if (count == 0) { | |
f52e47eb | 428 | return vector<T>(); |
bb85386d | 429 | } |
60f9d9a2 | 430 | |
37b5a2a0 | 431 | std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine()); |
bb85386d | 432 | |
f52e47eb | 433 | vector<T> result = {pick.begin(), pick.begin() + count}; |
60f9d9a2 | 434 | return result; |
435 | } | |
436 | ||
b7edebf8 | 437 | static bool getLatLon(const std::string& ip, double& lat, double& lon) |
438 | { | |
f9423419 | 439 | string inp = getGeo(ip, GeoIPInterface::Location); |
b7edebf8 | 440 | if(inp.empty()) |
441 | return false; | |
442 | lat=atof(inp.c_str()); | |
443 | auto pos=inp.find(' '); | |
444 | if(pos != string::npos) | |
445 | lon=atof(inp.c_str() + pos); | |
446 | return true; | |
447 | } | |
448 | ||
0540d223 | 449 | static bool getLatLon(const std::string& ip, string& loc) |
450 | { | |
451 | int latdeg, latmin, londeg, lonmin; | |
452 | double latsec, lonsec; | |
453 | char lathem='X', lonhem='X'; | |
1bc56192 | 454 | |
b17434c6 | 455 | double lat = 0, lon = 0; |
0540d223 | 456 | if(!getLatLon(ip, lat, lon)) |
457 | return false; | |
458 | ||
459 | if(lat > 0) { | |
460 | lathem='N'; | |
461 | } | |
462 | else { | |
463 | lat = -lat; | |
464 | lathem='S'; | |
465 | } | |
466 | ||
467 | if(lon > 0) { | |
468 | lonhem='E'; | |
469 | } | |
470 | else { | |
471 | lon = -lon; | |
472 | lonhem='W'; | |
473 | } | |
474 | ||
0540d223 | 475 | latdeg = lat; |
476 | latmin = (lat - latdeg)*60.0; | |
477 | latsec = (((lat - latdeg)*60.0) - latmin)*60.0; | |
478 | ||
479 | londeg = lon; | |
480 | lonmin = (lon - londeg)*60.0; | |
481 | lonsec = (((lon - londeg)*60.0) - lonmin)*60.0; | |
482 | ||
483 | // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m | |
484 | ||
485 | boost::format fmt("%d %d %d %c %d %d %d %c 0.00m 1.00m 10000.00m 10.00m"); | |
486 | ||
487 | loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str(); | |
488 | return true; | |
489 | } | |
975f0839 | 490 | |
1bc56192 | 491 | static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips) |
975f0839 | 492 | { |
6d7e1fd3 CHB |
493 | if (wips.empty()) { |
494 | throw std::invalid_argument("The IP list cannot be empty"); | |
495 | } | |
60f9d9a2 | 496 | map<double, vector<ComboAddress> > ranked; |
975f0839 | 497 | double wlat=0, wlon=0; |
498 | getLatLon(bestwho.toString(), wlat, wlon); | |
499 | // cout<<"bestwho "<<wlat<<", "<<wlon<<endl; | |
500 | vector<string> ret; | |
501 | for(const auto& c : wips) { | |
502 | double lat=0, lon=0; | |
503 | getLatLon(c.toString(), lat, lon); | |
504 | // cout<<c.toString()<<": "<<lat<<", "<<lon<<endl; | |
505 | double latdiff = wlat-lat; | |
506 | double londiff = wlon-lon; | |
507 | if(londiff > 180) | |
1bc56192 | 508 | londiff = 360 - londiff; |
975f0839 | 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); | |
512 | } | |
45d4e670 | 513 | return ranked.begin()->second[dns_random(ranked.begin()->second.size())]; |
975f0839 | 514 | } |
515 | ||
c9d40b1f | 516 | static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid) |
517 | { | |
18cb84d3 RG |
518 | static LockGuarded<UeberBackend> s_ub; |
519 | ||
c9d40b1f | 520 | DNSZoneRecord dr; |
521 | vector<DNSZoneRecord> ret; | |
18cb84d3 RG |
522 | { |
523 | auto ub = s_ub.lock(); | |
524 | ub->lookup(QType(qtype), name, zoneid); | |
525 | while (ub->get(dr)) { | |
526 | ret.push_back(dr); | |
527 | } | |
c9d40b1f | 528 | } |
529 | return ret; | |
530 | } | |
975f0839 | 531 | |
3bf2f94d PD |
532 | static bool getAuth(const DNSName& name, uint16_t qtype, SOAData* soaData) |
533 | { | |
534 | static LockGuarded<UeberBackend> s_ub; | |
535 | ||
3bf2f94d PD |
536 | { |
537 | auto ueback = s_ub.lock(); | |
538 | return ueback->getAuth(name, qtype, soaData); | |
539 | } | |
540 | } | |
541 | ||
2fe7bbf8 | 542 | static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue) |
5ca4872a | 543 | { |
2fe7bbf8 | 544 | string selector=defaultValue; |
5ca4872a | 545 | if(options) { |
2fe7bbf8 OV |
546 | if(options->count(name)) |
547 | selector=options->find(name)->second; | |
5ca4872a | 548 | } |
2fe7bbf8 OV |
549 | return selector; |
550 | } | |
551 | ||
552 | static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates) | |
553 | { | |
554 | vector<ComboAddress> ret; | |
5ca4872a | 555 | |
707cedf3 | 556 | if(selector=="all") |
2fe7bbf8 OV |
557 | return candidates; |
558 | else if(selector=="random") | |
f52e47eb | 559 | ret.emplace_back(pickRandom<ComboAddress>(candidates)); |
1bc56192 | 560 | else if(selector=="pickclosest") |
2fe7bbf8 | 561 | ret.emplace_back(pickclosest(bestwho, candidates)); |
5ca4872a | 562 | else if(selector=="hashed") |
f52e47eb | 563 | ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates)); |
2fe7bbf8 OV |
564 | else { |
565 | g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl; | |
f52e47eb | 566 | ret.emplace_back(pickRandom<ComboAddress>(candidates)); |
2fe7bbf8 OV |
567 | } |
568 | ||
569 | return ret; | |
570 | } | |
5ca4872a | 571 | |
60f9d9a2 | 572 | static vector<string> convComboAddressListToString(const vector<ComboAddress>& items) |
2fe7bbf8 | 573 | { |
60f9d9a2 | 574 | vector<string> result; |
575 | result.reserve(items.size()); | |
c9768b80 | 576 | |
60f9d9a2 | 577 | for (const auto& item : items) { |
578 | result.emplace_back(item.toString()); | |
c9768b80 CHB |
579 | } |
580 | ||
60f9d9a2 | 581 | return result; |
5ca4872a | 582 | } |
583 | ||
6c8714d2 | 584 | static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0) |
1bc56192 | 585 | { |
60f9d9a2 | 586 | vector<ComboAddress> result; |
587 | result.reserve(items.size()); | |
1bc56192 | 588 | |
60f9d9a2 | 589 | for(const auto& item : items) { |
6c8714d2 | 590 | result.emplace_back(ComboAddress(item.second, port)); |
c9768b80 | 591 | } |
1bc56192 | 592 | |
60f9d9a2 | 593 | return result; |
1bc56192 CHB |
594 | } |
595 | ||
04c53783 CHB |
596 | /** |
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'}} | |
600 | */ | |
601 | ||
602 | static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0) | |
603 | { | |
604 | vector<vector<ComboAddress>> candidates; | |
605 | ||
606 | if(auto simple = boost::get<iplist_t>(&items)) { | |
607 | vector<ComboAddress> unit = convComboAddressList(*simple, port); | |
608 | candidates.push_back(unit); | |
609 | } else { | |
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); | |
614 | } | |
615 | } | |
616 | return candidates; | |
617 | } | |
618 | ||
60f9d9a2 | 619 | static vector<string> convStringList(const iplist_t& items) |
1bc56192 | 620 | { |
60f9d9a2 | 621 | vector<string> result; |
622 | result.reserve(items.size()); | |
1bc56192 | 623 | |
60f9d9a2 | 624 | for(const auto& item : items) { |
625 | result.emplace_back(item.second); | |
c9768b80 | 626 | } |
1bc56192 | 627 | |
60f9d9a2 | 628 | return result; |
629 | } | |
630 | ||
60f9d9a2 | 631 | static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items) |
632 | { | |
633 | vector<pair<int,string> > result; | |
634 | result.reserve(items.size()); | |
635 | ||
636 | for(const auto& item : items) { | |
637 | result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2)); | |
638 | } | |
639 | ||
640 | return result; | |
1bc56192 CHB |
641 | } |
642 | ||
32829819 | 643 | bool g_LuaRecordSharedState; |
fd1bdfb3 | 644 | |
c13af350 | 645 | typedef struct AuthLuaRecordContext |
b7edebf8 | 646 | { |
c13af350 CHB |
647 | ComboAddress bestwho; |
648 | DNSName qname; | |
649 | DNSName zone; | |
650 | int zoneid; | |
651 | } lua_record_ctx_t; | |
1bc56192 | 652 | |
c13af350 | 653 | static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx; |
1bc56192 | 654 | |
4a0d414b CHB |
655 | /* |
656 | * Holds computed hashes for a given entry | |
657 | */ | |
a6be268f CHB |
658 | struct EntryHashesHolder |
659 | { | |
4a0d414b CHB |
660 | std::atomic<size_t> weight; |
661 | std::string entry; | |
662 | SharedLockGuarded<std::vector<unsigned int>> hashes; | |
a6be268f | 663 | std::atomic<time_t> lastUsed; |
4a0d414b | 664 | |
a6be268f | 665 | EntryHashesHolder(size_t weight_, std::string entry_, time_t lastUsed_ = time(nullptr)): weight(weight_), entry(std::move(entry_)), lastUsed(lastUsed_) { |
4a0d414b CHB |
666 | } |
667 | ||
668 | bool hashesComputed() { | |
669 | return weight == hashes.read_lock()->size(); | |
670 | } | |
671 | void hash() { | |
672 | auto locked = hashes.write_lock(); | |
673 | locked->clear(); | |
674 | locked->reserve(weight); | |
675 | size_t count = 0; | |
676 | while (count < weight) { | |
677 | auto value = boost::str(boost::format("%s-%d") % entry % count); | |
b4608224 CHB |
678 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) |
679 | auto whash = burtle(reinterpret_cast<const unsigned char*>(value.data()), value.size(), 0); | |
4a0d414b CHB |
680 | locked->push_back(whash); |
681 | ++count; | |
682 | } | |
683 | std::sort(locked->begin(), locked->end()); | |
684 | } | |
685 | }; | |
686 | ||
a6be268f CHB |
687 | using zone_hashes_key_t = std::tuple<int, std::string, std::string>; |
688 | ||
689 | static SharedLockGuarded<std::map< | |
690 | zone_hashes_key_t, // zoneid qname entry | |
4a0d414b | 691 | std::shared_ptr<EntryHashesHolder> // entry w/ corresponding hashes |
a6be268f | 692 | >> |
4a0d414b CHB |
693 | s_zone_hashes; |
694 | ||
a6be268f CHB |
695 | static std::atomic<time_t> s_lastConsistentHashesCleanup = 0; |
696 | ||
697 | /** | |
698 | * every ~g_luaConsistentHashesCleanupInterval, do a cleanup to delete entries that haven't been used in the last g_luaConsistentHashesExpireDelay | |
699 | */ | |
700 | static void cleanZoneHashes() | |
701 | { | |
702 | auto now = time(nullptr); | |
703 | if (s_lastConsistentHashesCleanup > (now - g_luaConsistentHashesCleanupInterval)) { | |
704 | return ; | |
705 | } | |
706 | s_lastConsistentHashesCleanup = now; | |
707 | std::vector<zone_hashes_key_t> toDelete{}; | |
708 | { | |
709 | auto locked = s_zone_hashes.read_lock(); | |
710 | auto someTimeAgo = now - g_luaConsistentHashesExpireDelay; | |
711 | ||
712 | for (const auto& [key, entry]: *locked) { | |
713 | if (entry->lastUsed > someTimeAgo) { | |
714 | toDelete.push_back(key); | |
715 | } | |
716 | } | |
717 | } | |
718 | if (!toDelete.empty()) { | |
719 | auto wlocked = s_zone_hashes.write_lock(); | |
720 | for (const auto& key : toDelete) { | |
721 | wlocked->erase(key); | |
722 | } | |
723 | } | |
724 | } | |
725 | ||
4a0d414b CHB |
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) |
727 | { | |
728 | std::vector<std::shared_ptr<EntryHashesHolder>> result{}; | |
a6be268f CHB |
729 | std::map<zone_hashes_key_t, std::shared_ptr<EntryHashesHolder>> newEntries{}; |
730 | ||
731 | { | |
732 | time_t now = time(nullptr); | |
733 | auto locked = s_zone_hashes.read_lock(); | |
4a0d414b | 734 | |
a6be268f CHB |
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); | |
739 | } else { | |
740 | locked->at(key)->weight = weight; | |
741 | locked->at(key)->lastUsed = now; | |
742 | result.push_back(locked->at(key)); | |
743 | } | |
744 | } | |
745 | } | |
746 | if (!newEntries.empty()) { | |
747 | auto wlocked = s_zone_hashes.write_lock(); | |
748 | ||
749 | for (auto& [key, entry]: newEntries) { | |
750 | result.push_back(entry); | |
751 | (*wlocked)[key] = std::move(entry); | |
4a0d414b | 752 | } |
4a0d414b CHB |
753 | } |
754 | ||
755 | return result; | |
756 | } | |
757 | ||
758 | static std::string pickConsistentWeightedHashed(const ComboAddress& bestwho, const std::vector<std::pair<int, std::string>>& items) | |
759 | { | |
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(); | |
764 | ||
765 | boost::optional<std::string> ret; | |
766 | boost::optional<std::string> first; | |
767 | ||
a6be268f CHB |
768 | cleanZoneHashes(); |
769 | ||
4a0d414b CHB |
770 | auto entries = getCHashedEntries(zoneId, queryName, items); |
771 | ||
772 | ComboAddress::addressOnlyHash addrOnlyHash; | |
773 | auto qhash = addrOnlyHash(bestwho); | |
774 | for (const auto& entry : entries) { | |
775 | if (!entry->hashesComputed()) { | |
776 | entry->hash(); | |
777 | } | |
778 | { | |
779 | const auto hashes = entry->hashes.read_lock(); | |
b4608224 | 780 | if (!hashes->empty()) { |
4a0d414b CHB |
781 | if (min > *(hashes->begin())) { |
782 | min = *(hashes->begin()); | |
783 | first = entry->entry; | |
784 | } | |
785 | ||
786 | auto hash_it = std::lower_bound(hashes->begin(), hashes->end(), qhash); | |
787 | if (hash_it != hashes->end()) { | |
788 | if (*hash_it < sel) { | |
789 | sel = *hash_it; | |
790 | ret = entry->entry; | |
791 | } | |
792 | } | |
793 | } | |
794 | } | |
795 | } | |
796 | if (ret != boost::none) { | |
797 | return *ret; | |
798 | } | |
799 | if (first != boost::none) { | |
800 | return *first; | |
801 | } | |
b4608224 | 802 | return {}; |
4a0d414b CHB |
803 | } |
804 | ||
224085cc RP |
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) |
806 | { | |
04c53783 CHB |
807 | vector<vector<ComboAddress> > candidates; |
808 | opts_t opts; | |
809 | if(options) | |
810 | opts = *options; | |
811 | ||
812 | candidates = convMultiComboAddressList(ips, port); | |
813 | ||
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); | |
819 | } | |
820 | } | |
821 | if(!available.empty()) { | |
822 | vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available); | |
823 | return convComboAddressListToString(res); | |
824 | } | |
825 | } | |
826 | ||
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()); | |
831 | } | |
832 | ||
833 | vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret); | |
834 | return convComboAddressListToString(res); | |
835 | } | |
836 | ||
dc03f001 | 837 | static void setupLuaRecords(LuaContext& lua) // NOLINT(readability-function-cognitive-complexity) |
c13af350 | 838 | { |
c13af350 | 839 | lua.writeFunction("latlon", []() { |
b17434c6 | 840 | double lat = 0, lon = 0; |
c13af350 | 841 | getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon); |
0540d223 | 842 | return std::to_string(lat)+" "+std::to_string(lon); |
843 | }); | |
c13af350 | 844 | lua.writeFunction("latlonloc", []() { |
0540d223 | 845 | string loc; |
c13af350 | 846 | getLatLon(s_lua_record_ctx->bestwho.toString(), loc); |
0540d223 | 847 | return loc; |
b7edebf8 | 848 | }); |
c13af350 | 849 | lua.writeFunction("closestMagic", []() { |
b7edebf8 | 850 | vector<ComboAddress> candidates; |
cc5c4f6b | 851 | // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org |
c13af350 | 852 | for(auto l : s_lua_record_ctx->qname.getRawLabels()) { |
b7edebf8 | 853 | boost::replace_all(l, "-", "."); |
b7edebf8 | 854 | try { |
855 | candidates.emplace_back(l); | |
6d7e1fd3 | 856 | } catch (const PDNSException& e) { |
cc5c4f6b CHB |
857 | // no need to continue as we most likely reached the end of the ip list |
858 | break ; | |
b7edebf8 | 859 | } |
860 | } | |
c13af350 | 861 | return pickclosest(s_lua_record_ctx->bestwho, candidates).toString(); |
b7edebf8 | 862 | }); |
c13af350 CHB |
863 | lua.writeFunction("latlonMagic", [](){ |
864 | auto labels= s_lua_record_ctx->qname.getRawLabels(); | |
7fedb52a | 865 | if(labels.size()<4) |
866 | return std::string("unknown"); | |
b17434c6 | 867 | double lat = 0, lon = 0; |
7fedb52a | 868 | getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon); |
869 | return std::to_string(lat)+" "+std::to_string(lon); | |
870 | }); | |
c9d40b1f | 871 | |
1bc56192 | 872 | |
54c20d39 | 873 | lua.writeFunction("createReverse", [](string format, boost::optional<std::unordered_map<string,string>> e){ |
c9d40b1f | 874 | try { |
c13af350 CHB |
875 | auto labels = s_lua_record_ctx->qname.getRawLabels(); |
876 | if(labels.size()<4) | |
877 | return std::string("unknown"); | |
bb85386d | 878 | |
c13af350 | 879 | vector<ComboAddress> candidates; |
bb85386d | 880 | |
c13af350 | 881 | // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa |
223bfcad | 882 | // e["1.2.3.4"]="bert.powerdns.com" then provides an exception |
c13af350 CHB |
883 | if(e) { |
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) | |
888 | return c.second; | |
889 | } | |
54c20d39 | 890 | boost::format fmt(format); |
c13af350 CHB |
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]; | |
bb85386d | 893 | |
c13af350 | 894 | fmt % (labels[3]+"-"+labels[2]+"-"+labels[1]+"-"+labels[0]); |
c9d40b1f | 895 | |
c13af350 CHB |
896 | boost::format fmt2("%02x%02x%02x%02x"); |
897 | for(int i=3; i>=0; --i) | |
898 | fmt2 % atoi(labels[i].c_str()); | |
c9d40b1f | 899 | |
c13af350 | 900 | fmt % (fmt2.str()); |
c9d40b1f | 901 | |
c13af350 | 902 | return fmt.str(); |
c9d40b1f | 903 | } |
af6bf47c OM |
904 | catch(std::exception& ex) { |
905 | g_log<<Logger::Error<<"error: "<<ex.what()<<endl; | |
c9d40b1f | 906 | } |
907 | return std::string("error"); | |
908 | }); | |
c13af350 | 909 | lua.writeFunction("createForward", []() { |
448e7a2d | 910 | static string allZerosIP("0.0.0.0"); |
c13af350 | 911 | DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone); |
448e7a2d PL |
912 | // parts is something like ["1", "2", "3", "4", "static"] or |
913 | // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...] | |
c9d40b1f | 914 | auto parts = rel.getRawLabels(); |
448e7a2d PL |
915 | // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply... |
916 | if(parts.size()>=4) { | |
917 | try { | |
918 | ComboAddress ca(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]); | |
919 | return ca.toString(); | |
920 | } catch (const PDNSException &e) { | |
921 | return allZerosIP; | |
922 | } | |
d3cd0635 | 923 | } else if (!parts.empty()) { |
c06dda72 | 924 | auto& input = parts.at(0); |
f7deb7d0 PD |
925 | |
926 | // allow a word without - in front, as long as it does not contain anything that could be a number | |
f8cbdf3e | 927 | size_t nonhexprefix = strcspn(input.c_str(), "0123456789abcdefABCDEF"); |
f7deb7d0 | 928 | if (nonhexprefix > 0) { |
684ea0ef | 929 | input = input.substr(nonhexprefix); |
f7deb7d0 PD |
930 | } |
931 | ||
c9d40b1f | 932 | // either hex string, or 12-13-14-15 |
448e7a2d | 933 | vector<string> ip_parts; |
c06dda72 | 934 | |
684ea0ef | 935 | stringtok(ip_parts, input, "-"); |
df6ad4ee | 936 | unsigned int x1, x2, x3, x4; |
448e7a2d PL |
937 | if (ip_parts.size() >= 4) { |
938 | // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4) | |
939 | string ret; | |
7ced3b1a PD |
940 | for (size_t index=4; index > 0; index--) { |
941 | auto octet = ip_parts[ip_parts.size() - index]; | |
448e7a2d PL |
942 | try { |
943 | auto octetVal = std::stol(octet); | |
944 | if (octetVal >= 0 && octetVal <= 255) { | |
7ced3b1a | 945 | ret += ip_parts.at(ip_parts.size() - index) + "."; |
448e7a2d PL |
946 | } else { |
947 | return allZerosIP; | |
948 | } | |
949 | } catch (const std::exception &e) { | |
950 | return allZerosIP; | |
951 | } | |
952 | } | |
953 | ret.resize(ret.size() - 1); // remove trailing dot after last octet | |
954 | return ret; | |
061c3e9f PD |
955 | } |
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) { | |
5fb49130 | 959 | return std::to_string(x1) + "." + std::to_string(x2) + "." + std::to_string(x3) + "." + std::to_string(x4); |
061c3e9f | 960 | } |
c9d40b1f | 961 | } |
c9d40b1f | 962 | } |
448e7a2d | 963 | return allZerosIP; |
c9d40b1f | 964 | }); |
965 | ||
c13af350 CHB |
966 | lua.writeFunction("createForward6", []() { |
967 | DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone); | |
c9d40b1f | 968 | auto parts = rel.getRawLabels(); |
969 | if(parts.size()==8) { | |
970 | string tot; | |
971 | for(int i=0; i<8; ++i) { | |
972 | if(i) | |
973 | tot.append(1,':'); | |
974 | tot+=parts[i]; | |
975 | } | |
976 | ComboAddress ca(tot); | |
977 | return ca.toString(); | |
978 | } | |
979 | else if(parts.size()==1) { | |
ada6063a PD |
980 | if (parts[0].find('-') != std::string::npos) { |
981 | boost::replace_all(parts[0],"-",":"); | |
982 | ComboAddress ca(parts[0]); | |
983 | return ca.toString(); | |
984 | } else { | |
985 | if (parts[0].size() >= 32) { | |
986 | auto ippart = parts[0].substr(parts[0].size()-32); | |
987 | auto fulladdress = | |
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); | |
996 | ||
997 | ComboAddress ca(fulladdress); | |
998 | return ca.toString(); | |
999 | } | |
1000 | } | |
c9d40b1f | 1001 | } |
1002 | ||
1003 | return std::string("::"); | |
1004 | }); | |
54c20d39 | 1005 | lua.writeFunction("createReverse6", [](string format, boost::optional<std::unordered_map<string,string>> e){ |
c9d40b1f | 1006 | vector<ComboAddress> candidates; |
1007 | ||
1008 | try { | |
c13af350 | 1009 | auto labels= s_lua_record_ctx->qname.getRawLabels(); |
c9d40b1f | 1010 | if(labels.size()<32) |
1011 | return std::string("unknown"); | |
54c20d39 | 1012 | boost::format fmt(format); |
c9d40b1f | 1013 | fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) ); |
1bc56192 | 1014 | |
c9d40b1f | 1015 | |
1016 | string together; | |
1017 | vector<string> quads; | |
1018 | for(int i=0; i<8; ++i) { | |
1019 | if(i) | |
1020 | together+=":"; | |
a3739f30 | 1021 | string lquad; |
c9d40b1f | 1022 | for(int j=0; j <4; ++j) { |
a3739f30 | 1023 | lquad.append(1, labels[31-i*4-j][0]); |
c9d40b1f | 1024 | together += labels[31-i*4-j][0]; |
1025 | } | |
a3739f30 | 1026 | quads.push_back(lquad); |
c9d40b1f | 1027 | } |
1028 | ComboAddress ip6(together,0); | |
1029 | ||
1030 | if(e) { | |
1031 | auto& addrs=*e; | |
1032 | for(const auto& addr: addrs) { | |
1033 | // this makes sure we catch all forms of the address | |
1034 | if(ComboAddress(addr.first,0)==ip6) | |
1035 | return addr.second; | |
1036 | } | |
1037 | } | |
1bc56192 | 1038 | |
c9d40b1f | 1039 | string dashed=ip6.toString(); |
1040 | boost::replace_all(dashed, ":", "-"); | |
1bc56192 | 1041 | |
c9d40b1f | 1042 | for(int i=31; i>=0; --i) |
1043 | fmt % labels[i]; | |
1044 | fmt % dashed; | |
1045 | ||
a3739f30 OM |
1046 | for(const auto& lquad : quads) |
1047 | fmt % lquad; | |
1bc56192 | 1048 | |
c9d40b1f | 1049 | return fmt.str(); |
1050 | } | |
af6bf47c | 1051 | catch(std::exception& ex) { |
e7929442 | 1052 | g_log<<Logger::Error<<"LUA Record exception: "<<ex.what()<<endl; |
c9d40b1f | 1053 | } |
af6bf47c OM |
1054 | catch(PDNSException& ex) { |
1055 | g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl; | |
c9d40b1f | 1056 | } |
1057 | return std::string("unknown"); | |
1058 | }); | |
1059 | ||
863fa7f6 | 1060 | lua.writeFunction("filterForward", [](const string& address, NetmaskGroup& nmg, boost::optional<string> fallback) -> vector<string> { |
223bfcad PD |
1061 | ComboAddress ca(address); |
1062 | ||
1063 | if (nmg.match(ComboAddress(address))) { | |
863fa7f6 | 1064 | return {address}; |
223bfcad PD |
1065 | } else { |
1066 | if (fallback) { | |
98301eb0 PD |
1067 | if (fallback->empty()) { |
1068 | // if fallback is an empty string, return an empty array | |
863fa7f6 | 1069 | return {}; |
98301eb0 | 1070 | } |
863fa7f6 | 1071 | return {*fallback}; |
223bfcad PD |
1072 | } |
1073 | ||
1074 | if (ca.isIPv4()) { | |
863fa7f6 | 1075 | return {string("0.0.0.0")}; |
223bfcad | 1076 | } else { |
863fa7f6 | 1077 | return {string("::")}; |
223bfcad PD |
1078 | } |
1079 | } | |
1080 | }); | |
1081 | ||
1bc56192 CHB |
1082 | /* |
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 | |
ef2ea4bf | 1086 | * addresses supplied for testing. |
1bc56192 CHB |
1087 | * |
1088 | * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })" | |
1089 | */ | |
6c8714d2 | 1090 | lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, const boost::optional<std::unordered_map<string,string>> options) { |
6c8714d2 CHB |
1091 | if (port < 0) { |
1092 | port = 0; | |
1093 | } | |
1094 | if (port > std::numeric_limits<uint16_t>::max()) { | |
1095 | port = std::numeric_limits<uint16_t>::max(); | |
1096 | } | |
6c8714d2 | 1097 | |
04c53783 CHB |
1098 | auto checker = [](const ComboAddress& addr, const opts_t& opts) { |
1099 | return g_up.isUp(addr, opts); | |
1100 | }; | |
224085cc | 1101 | return genericIfUp(ips, options, checker, port); |
1bc56192 | 1102 | }); |
b7edebf8 | 1103 | |
95d0df69 PD |
1104 | lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) { |
1105 | vector<ComboAddress> candidates; | |
1106 | opts_t opts; | |
1107 | if(options) | |
1108 | opts = *options; | |
1109 | ||
1110 | ComboAddress ca_unspec; | |
1111 | ca_unspec.sin4.sin_family=AF_UNSPEC; | |
1112 | ||
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; | |
1117 | ||
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); | |
1124 | } | |
1125 | } | |
1126 | if(!available.empty()) { | |
1127 | vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available); | |
60f9d9a2 | 1128 | return convComboAddressListToString(res); |
95d0df69 PD |
1129 | } |
1130 | } | |
1131 | ||
1132 | // All units down, apply backupSelector on all candidates | |
1133 | vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates); | |
60f9d9a2 | 1134 | return convComboAddressListToString(res); |
95d0df69 PD |
1135 | }); |
1136 | ||
c13af350 | 1137 | lua.writeFunction("ifurlup", [](const std::string& url, |
1bc56192 CHB |
1138 | const boost::variant<iplist_t, ipunitlist_t>& ips, |
1139 | boost::optional<opts_t> options) { | |
b7edebf8 | 1140 | |
04c53783 CHB |
1141 | auto checker = [&url](const ComboAddress& addr, const opts_t& opts) { |
1142 | return g_up.isUp(addr, url, opts); | |
1143 | }; | |
1144 | return genericIfUp(ips, options, checker); | |
2fe7bbf8 | 1145 | }); |
1bc56192 CHB |
1146 | /* |
1147 | * Returns a random IP address from the supplied list | |
1148 | * @example pickrandom({ '1.2.3.4', '5.4.3.2' })" | |
1149 | */ | |
1150 | lua.writeFunction("pickrandom", [](const iplist_t& ips) { | |
60f9d9a2 | 1151 | vector<string> items = convStringList(ips); |
f52e47eb | 1152 | return pickRandom<string>(items); |
b7edebf8 | 1153 | }); |
1154 | ||
60f9d9a2 | 1155 | lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) { |
1156 | vector<string> items = convStringList(ips); | |
f52e47eb | 1157 | return pickRandomSample<string>(n, items); |
60f9d9a2 | 1158 | }); |
b7edebf8 | 1159 | |
60f9d9a2 | 1160 | lua.writeFunction("pickhashed", [](const iplist_t& ips) { |
1161 | vector<string> items = convStringList(ips); | |
f52e47eb | 1162 | return pickHashed<string>(s_lua_record_ctx->bestwho, items); |
60f9d9a2 | 1163 | }); |
1bc56192 CHB |
1164 | /* |
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'} }) | |
1168 | */ | |
1169 | lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) { | |
60f9d9a2 | 1170 | vector< pair<int, string> > items = convIntStringPairList(ips); |
f52e47eb | 1171 | return pickWeightedRandom<string>(items); |
975f0839 | 1172 | }); |
1173 | ||
1bc56192 CHB |
1174 | /* |
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'} }) | |
1178 | */ | |
c13af350 | 1179 | lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) { |
60f9d9a2 | 1180 | vector< pair<int, string> > items; |
1bc56192 | 1181 | |
60f9d9a2 | 1182 | items.reserve(ips.size()); |
b4608224 CHB |
1183 | for (auto& entry : ips) { |
1184 | items.emplace_back(atoi(entry.second[1].c_str()), entry.second[2]); | |
4a0d414b | 1185 | } |
1bc56192 | 1186 | |
f52e47eb | 1187 | return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items); |
975f0839 | 1188 | }); |
1189 | ||
d33b8bf4 BR |
1190 | /* |
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'} }) | |
1194 | */ | |
1195 | lua.writeFunction("picknamehashed", [](std::unordered_map<int, wiplist_t > ips) { | |
1196 | vector< pair<int, string> > items; | |
1197 | ||
1198 | items.reserve(ips.size()); | |
1199 | for(auto& i : ips) | |
dc03f001 | 1200 | { |
d33b8bf4 | 1201 | items.emplace_back(atoi(i.second[1].c_str()), i.second[2]); |
dc03f001 | 1202 | } |
d33b8bf4 BR |
1203 | |
1204 | return pickWeightedNameHashed<string>(s_lua_record_ctx->qname, items); | |
1205 | }); | |
4a0d414b CHB |
1206 | /* |
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'} }) | |
1210 | */ | |
a6be268f CHB |
1211 | lua.writeFunction("pickchashed", [](const std::unordered_map<int, wiplist_t>& ips) { |
1212 | std::vector<std::pair<int, std::string>> items; | |
4a0d414b CHB |
1213 | |
1214 | items.reserve(ips.size()); | |
a6be268f CHB |
1215 | for (const auto& entry : ips) { |
1216 | items.emplace_back(atoi(entry.second.at(1).c_str()), entry.second.at(2)); | |
4a0d414b CHB |
1217 | } |
1218 | ||
1219 | return pickConsistentWeightedHashed(s_lua_record_ctx->bestwho, items); | |
1220 | }); | |
975f0839 | 1221 | |
c13af350 | 1222 | lua.writeFunction("pickclosest", [](const iplist_t& ips) { |
60f9d9a2 | 1223 | vector<ComboAddress> conv = convComboAddressList(ips); |
1bc56192 | 1224 | |
c13af350 | 1225 | return pickclosest(s_lua_record_ctx->bestwho, conv).toString(); |
1bc56192 | 1226 | |
b7edebf8 | 1227 | }); |
1228 | ||
c13af350 CHB |
1229 | if (g_luaRecordExecLimit > 0) { |
1230 | lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit)); | |
1231 | } | |
1bc56192 | 1232 | |
d73de874 | 1233 | lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){ |
b7edebf8 | 1234 | throw std::runtime_error("Script took too long"); |
1235 | }); | |
b7edebf8 | 1236 | |
f9423419 | 1237 | lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) { |
1d56dac6 PD |
1238 | return getGeo(ip, attr); |
1239 | }); | |
1240 | ||
b7edebf8 | 1241 | typedef const boost::variant<string,vector<pair<int,string> > > combovar_t; |
60f9d9a2 | 1242 | |
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()); | |
1247 | }); | |
1248 | }); | |
c13af350 CHB |
1249 | lua.writeFunction("continent", [](const combovar_t& continent) { |
1250 | string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent); | |
b7edebf8 | 1251 | return doCompare(continent, res, [](const std::string& a, const std::string& b) { |
1252 | return !strcasecmp(a.c_str(), b.c_str()); | |
1253 | }); | |
1254 | }); | |
60f9d9a2 | 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("--"); | |
1260 | } | |
1261 | return res; | |
1262 | }); | |
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) { | |
b7edebf8 | 1266 | return !strcasecmp(a.c_str(), b.c_str()); |
1267 | }); | |
60f9d9a2 | 1268 | |
b7edebf8 | 1269 | }); |
60f9d9a2 | 1270 | lua.writeFunction("countryCode", []() { |
1271 | string unknown("unknown"); | |
c13af350 | 1272 | string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2); |
60f9d9a2 | 1273 | if ( res == unknown ) { |
1274 | return std::string("--"); | |
1275 | } | |
1276 | return res; | |
1277 | }); | |
1278 | lua.writeFunction("region", [](const combovar_t& var) { | |
1279 | string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region); | |
b7edebf8 | 1280 | return doCompare(var, res, [](const std::string& a, const std::string& b) { |
1281 | return !strcasecmp(a.c_str(), b.c_str()); | |
1282 | }); | |
1bc56192 | 1283 | |
b7edebf8 | 1284 | }); |
60f9d9a2 | 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("--"); | |
1290 | } | |
1291 | return res; | |
1292 | }); | |
c13af350 | 1293 | lua.writeFunction("netmask", [](const iplist_t& ips) { |
b7edebf8 | 1294 | for(const auto& i :ips) { |
1295 | Netmask nm(i.second); | |
c13af350 | 1296 | if(nm.match(s_lua_record_ctx->bestwho)) |
b7edebf8 | 1297 | return true; |
1298 | } | |
1299 | return false; | |
1300 | }); | |
b7edebf8 | 1301 | /* { |
1302 | { | |
1bc56192 | 1303 | {'192.168.0.0/16', '10.0.0.0/8'}, |
b7edebf8 | 1304 | {'192.168.20.20', '192.168.20.21'} |
1305 | }, | |
1306 | { | |
1307 | {'0.0.0.0/0'}, {'192.0.2.1'} | |
1308 | } | |
1309 | } | |
1bc56192 | 1310 | */ |
c13af350 | 1311 | lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) { |
b7edebf8 | 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); | |
c13af350 | 1317 | if(nm.match(s_lua_record_ctx->bestwho)) { |
28beeb0f PD |
1318 | if (destinations.empty()) { |
1319 | throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm.toString() + ")"); | |
1320 | } | |
b51ef4f9 | 1321 | return destinations[dns_random(destinations.size())].second; |
b7edebf8 | 1322 | } |
1323 | } | |
1324 | } | |
1325 | return std::string(); | |
60f9d9a2 | 1326 | }); |
1bc56192 | 1327 | |
60f9d9a2 | 1328 | lua.writeFunction("all", [](const vector< pair<int,string> >& ips) { |
1329 | vector<string> result; | |
1330 | result.reserve(ips.size()); | |
bb85386d | 1331 | |
60f9d9a2 | 1332 | for(const auto& ip : ips) { |
1333 | result.emplace_back(ip.second); | |
1334 | } | |
1335 | if(result.empty()) { | |
1336 | throw std::invalid_argument("The IP list cannot be empty"); | |
1337 | } | |
1338 | return result; | |
1339 | }); | |
1bc56192 | 1340 | |
2ef08930 | 1341 | lua.writeFunction("dblookup", [](const string& record, uint16_t qtype) { |
3bf2f94d | 1342 | DNSName rec; |
3bf2f94d PD |
1343 | vector<string> ret; |
1344 | try { | |
1345 | rec = DNSName(record); | |
3bf2f94d PD |
1346 | } |
1347 | catch (const std::exception& e) { | |
2ef08930 | 1348 | g_log << Logger::Error << "DB lookup cannot be performed, the name (" << record << ") is malformed: " << e.what() << endl; |
3bf2f94d PD |
1349 | return ret; |
1350 | } | |
1351 | try { | |
1352 | SOAData soaData; | |
1353 | ||
1354 | if (!getAuth(rec, qtype, &soaData)) { | |
1355 | return ret; | |
1356 | } | |
1357 | ||
1358 | vector<DNSZoneRecord> drs = lookup(rec, qtype, soaData.domain_id); | |
1359 | for (const auto& drec : drs) { | |
1360 | ret.push_back(drec.dr.getContent()->getZoneRepresentation()); | |
1361 | } | |
1362 | } | |
1363 | catch (std::exception& e) { | |
33826bd1 | 1364 | g_log << Logger::Error << "Failed to do DB lookup for " << rec << "/" << qtype << ": " << e.what() << endl; |
3bf2f94d PD |
1365 | } |
1366 | return ret; | |
1367 | }); | |
1368 | ||
c13af350 | 1369 | lua.writeFunction("include", [&lua](string record) { |
388d3a43 | 1370 | DNSName rec; |
b7edebf8 | 1371 | try { |
388d3a43 PL |
1372 | rec = DNSName(record) + s_lua_record_ctx->zone; |
1373 | } catch (const std::exception &e){ | |
51a4e451 | 1374 | g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl; |
388d3a43 PL |
1375 | return; |
1376 | } | |
1377 | try { | |
1378 | vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zoneid); | |
c9d40b1f | 1379 | for(const auto& dr : drs) { |
b7edebf8 | 1380 | auto lr = getRR<LUARecordContent>(dr.dr); |
1381 | lua.executeCode(lr->getCode()); | |
1382 | } | |
25bcfaec | 1383 | } |
1384 | catch(std::exception& e) { | |
388d3a43 | 1385 | g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<rec<<": "<<e.what()<<endl; |
25bcfaec | 1386 | } |
b7edebf8 | 1387 | }); |
c13af350 CHB |
1388 | } |
1389 | ||
87f1cd7c | 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) |
c13af350 | 1391 | { |
87f1cd7c | 1392 | if(!LUA || // we don't have a Lua state yet |
c13af350 | 1393 | !g_LuaRecordSharedState) { // or we want a new one even if we had one |
87f1cd7c PD |
1394 | LUA = make_unique<AuthLua4>(); |
1395 | setupLuaRecords(*LUA->getLua()); | |
c13af350 CHB |
1396 | } |
1397 | ||
1398 | std::vector<shared_ptr<DNSRecordContent>> ret; | |
1399 | ||
87f1cd7c | 1400 | LuaContext& lua = *LUA->getLua(); |
c13af350 | 1401 | |
2bbc9eb0 | 1402 | s_lua_record_ctx = std::make_unique<lua_record_ctx_t>(); |
c13af350 CHB |
1403 | s_lua_record_ctx->qname = query; |
1404 | s_lua_record_ctx->zone = zone; | |
1405 | s_lua_record_ctx->zoneid = zoneid; | |
bb85386d | 1406 | |
c13af350 CHB |
1407 | lua.writeVariable("qname", query); |
1408 | lua.writeVariable("zone", zone); | |
1409 | lua.writeVariable("zoneid", zoneid); | |
4172a5b2 | 1410 | lua.writeVariable("who", dnsp.getInnerRemote()); |
514df45b | 1411 | lua.writeVariable("localwho", dnsp.getLocal()); |
c13af350 CHB |
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(); | |
1419 | } | |
1420 | else { | |
1421 | lua.writeVariable("ecswho", nullptr); | |
4172a5b2 | 1422 | s_lua_record_ctx->bestwho = dnsp.getInnerRemote(); |
c13af350 CHB |
1423 | } |
1424 | lua.writeVariable("bestwho", s_lua_record_ctx->bestwho); | |
b7edebf8 | 1425 | |
b7edebf8 | 1426 | try { |
1427 | string actual; | |
1428 | if(!code.empty() && code[0]!=';') | |
6180eab4 PD |
1429 | actual = "return " + code; |
1430 | else | |
1431 | actual = code.substr(1); | |
c9d40b1f | 1432 | |
b7edebf8 | 1433 | auto content=lua.executeCode<boost::variant<string, vector<pair<int, string> > > >(actual); |
c9d40b1f | 1434 | |
b7edebf8 | 1435 | vector<string> contents; |
1436 | if(auto str = boost::get<string>(&content)) | |
1437 | contents.push_back(*str); | |
1438 | else | |
1439 | for(const auto& c : boost::get<vector<pair<int,string>>>(content)) | |
1440 | contents.push_back(c.second); | |
1bc56192 | 1441 | |
690b86b7 | 1442 | for(const auto& content_it: contents) { |
b7edebf8 | 1443 | if(qtype==QType::TXT) |
d525b58b | 1444 | ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"')); |
b7edebf8 | 1445 | else |
d525b58b | 1446 | ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it)); |
b7edebf8 | 1447 | } |
1bc56192 | 1448 | } catch(std::exception &e) { |
d5fcd583 | 1449 | g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what(); |
3217f4ea PD |
1450 | try { |
1451 | std::rethrow_if_nested(e); | |
1452 | g_log<<endl; | |
1453 | } catch(const std::exception& ne) { | |
1454 | g_log << ": " << ne.what() << std::endl; | |
1455 | } | |
1456 | catch(const PDNSException& ne) { | |
1457 | g_log << ": " << ne.reason << std::endl; | |
1458 | } | |
1bc56192 | 1459 | throw ; |
b7edebf8 | 1460 | } |
1461 | ||
1462 | return ret; | |
1463 | } |