]>
Commit | Line | Data |
---|---|---|
2c010a9f | 1 | #include <thread> |
a6897a16 | 2 | #include <future> |
a6897a16 | 3 | #include <boost/format.hpp> |
4de01aaa | 4 | #include <utility> |
60f9d9a2 | 5 | #include <algorithm> |
6 | #include <random> | |
192e15fc | 7 | #include "version.hh" |
b7edebf8 | 8 | #include "ext/luawrapper/include/LuaContext.hpp" |
18cb84d3 | 9 | #include "lock.hh" |
b7edebf8 | 10 | #include "lua-auth4.hh" |
b7edebf8 | 11 | #include "sstuff.hh" |
b7edebf8 | 12 | #include "minicurl.hh" |
13 | #include "ueberbackend.hh" | |
8900e2e3 | 14 | #include "dnsrecords.hh" |
45d4e670 | 15 | #include "dns_random.hh" |
8cb70f23 | 16 | #include "auth-main.hh" |
f9423419 | 17 | #include "../modules/geoipbackend/geoipinterface.hh" // only for the enum |
0540d223 | 18 | |
19 | /* to do: | |
25bcfaec | 20 | block AXFR unless TSIG, or override |
69ec43c3 | 21 | |
22 | investigate IPv6 | |
23 | ||
24 | check the wildcard 'no cache' stuff, we may get it wrong | |
25 | ||
26 | ponder ECS scopemask setting | |
27 | ||
28 | ponder netmask tree from file for huge number of netmasks | |
25bcfaec | 29 | |
25512599 | 30 | unify ifurlup/ifportup |
69ec43c3 | 31 | add attribute for certificate check |
0540d223 | 32 | add list of current monitors |
33 | expire them too? | |
c9d40b1f | 34 | |
35 | pool of UeberBackends? | |
1bc56192 CHB |
36 | |
37 | Pool checks ? | |
0540d223 | 38 | */ |
39 | ||
af68014f CHB |
40 | extern int g_luaRecordExecLimit; |
41 | ||
1bc56192 CHB |
42 | using iplist_t = vector<pair<int, string> >; |
43 | using wiplist_t = std::unordered_map<int, string>; | |
44 | using ipunitlist_t = vector<pair<int, iplist_t> >; | |
45 | using opts_t = std::unordered_map<string,string>; | |
46 | ||
b7edebf8 | 47 | class IsUpOracle |
48 | { | |
49 | private: | |
b7edebf8 | 50 | struct CheckDesc |
51 | { | |
52 | ComboAddress rem; | |
53 | string url; | |
54 | opts_t opts; | |
55 | bool operator<(const CheckDesc& rhs) const | |
56 | { | |
25bcfaec | 57 | std::map<string,string> oopts, rhsoopts; |
58 | for(const auto& m : opts) | |
59 | oopts[m.first]=m.second; | |
60 | for(const auto& m : rhs.opts) | |
61 | rhsoopts[m.first]=m.second; | |
1bc56192 | 62 | |
0b0882f5 RP |
63 | return std::tuple(rem, url, oopts) < |
64 | std::tuple(rhs.rem, rhs.url, rhsoopts); | |
b7edebf8 | 65 | } |
66 | }; | |
a6897a16 CHB |
67 | struct CheckState |
68 | { | |
69 | CheckState(time_t _lastAccess): lastAccess(_lastAccess) {} | |
70 | /* current status */ | |
71 | std::atomic<bool> status{false}; | |
72 | /* first check ? */ | |
73 | std::atomic<bool> first{true}; | |
74 | /* last time the status was accessed */ | |
75 | std::atomic<time_t> lastAccess{0}; | |
76 | }; | |
77 | ||
b7edebf8 | 78 | public: |
d27a2d30 | 79 | IsUpOracle() |
a6897a16 | 80 | { |
d27a2d30 | 81 | d_checkerThreadStarted.clear(); |
a6897a16 CHB |
82 | } |
83 | ~IsUpOracle() | |
84 | { | |
a6897a16 | 85 | } |
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 PG |
362 | template <typename T> |
363 | static T pickWeightedHashed(const ComboAddress& bestwho, 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 | ||
f52e47eb | 387 | template <typename T> |
bb85386d | 388 | static vector<T> pickRandomSample(int n, const vector<T>& items) |
60f9d9a2 | 389 | { |
390 | if (items.empty()) { | |
391 | throw std::invalid_argument("The items list cannot be empty"); | |
392 | } | |
bb85386d | 393 | |
f52e47eb | 394 | vector<T> pick; |
60f9d9a2 | 395 | pick.reserve(items.size()); |
bb85386d | 396 | |
60f9d9a2 | 397 | for(auto& item : items) { |
398 | pick.push_back(item); | |
399 | } | |
bb85386d | 400 | |
60f9d9a2 | 401 | int count = std::min(std::max<size_t>(0, n), items.size()); |
402 | ||
403 | if (count == 0) { | |
f52e47eb | 404 | return vector<T>(); |
bb85386d | 405 | } |
60f9d9a2 | 406 | |
37b5a2a0 | 407 | std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine()); |
bb85386d | 408 | |
f52e47eb | 409 | vector<T> result = {pick.begin(), pick.begin() + count}; |
60f9d9a2 | 410 | return result; |
411 | } | |
412 | ||
b7edebf8 | 413 | static bool getLatLon(const std::string& ip, double& lat, double& lon) |
414 | { | |
f9423419 | 415 | string inp = getGeo(ip, GeoIPInterface::Location); |
b7edebf8 | 416 | if(inp.empty()) |
417 | return false; | |
418 | lat=atof(inp.c_str()); | |
419 | auto pos=inp.find(' '); | |
420 | if(pos != string::npos) | |
421 | lon=atof(inp.c_str() + pos); | |
422 | return true; | |
423 | } | |
424 | ||
0540d223 | 425 | static bool getLatLon(const std::string& ip, string& loc) |
426 | { | |
427 | int latdeg, latmin, londeg, lonmin; | |
428 | double latsec, lonsec; | |
429 | char lathem='X', lonhem='X'; | |
1bc56192 | 430 | |
b17434c6 | 431 | double lat = 0, lon = 0; |
0540d223 | 432 | if(!getLatLon(ip, lat, lon)) |
433 | return false; | |
434 | ||
435 | if(lat > 0) { | |
436 | lathem='N'; | |
437 | } | |
438 | else { | |
439 | lat = -lat; | |
440 | lathem='S'; | |
441 | } | |
442 | ||
443 | if(lon > 0) { | |
444 | lonhem='E'; | |
445 | } | |
446 | else { | |
447 | lon = -lon; | |
448 | lonhem='W'; | |
449 | } | |
450 | ||
0540d223 | 451 | latdeg = lat; |
452 | latmin = (lat - latdeg)*60.0; | |
453 | latsec = (((lat - latdeg)*60.0) - latmin)*60.0; | |
454 | ||
455 | londeg = lon; | |
456 | lonmin = (lon - londeg)*60.0; | |
457 | lonsec = (((lon - londeg)*60.0) - lonmin)*60.0; | |
458 | ||
459 | // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m | |
460 | ||
461 | boost::format fmt("%d %d %d %c %d %d %d %c 0.00m 1.00m 10000.00m 10.00m"); | |
462 | ||
463 | loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str(); | |
464 | return true; | |
465 | } | |
975f0839 | 466 | |
1bc56192 | 467 | static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips) |
975f0839 | 468 | { |
6d7e1fd3 CHB |
469 | if (wips.empty()) { |
470 | throw std::invalid_argument("The IP list cannot be empty"); | |
471 | } | |
60f9d9a2 | 472 | map<double, vector<ComboAddress> > ranked; |
975f0839 | 473 | double wlat=0, wlon=0; |
474 | getLatLon(bestwho.toString(), wlat, wlon); | |
475 | // cout<<"bestwho "<<wlat<<", "<<wlon<<endl; | |
476 | vector<string> ret; | |
477 | for(const auto& c : wips) { | |
478 | double lat=0, lon=0; | |
479 | getLatLon(c.toString(), lat, lon); | |
480 | // cout<<c.toString()<<": "<<lat<<", "<<lon<<endl; | |
481 | double latdiff = wlat-lat; | |
482 | double londiff = wlon-lon; | |
483 | if(londiff > 180) | |
1bc56192 | 484 | londiff = 360 - londiff; |
975f0839 | 485 | double dist2=latdiff*latdiff + londiff*londiff; |
486 | // cout<<" distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree | |
487 | ranked[dist2].push_back(c); | |
488 | } | |
45d4e670 | 489 | return ranked.begin()->second[dns_random(ranked.begin()->second.size())]; |
975f0839 | 490 | } |
491 | ||
c9d40b1f | 492 | static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid) |
493 | { | |
18cb84d3 RG |
494 | static LockGuarded<UeberBackend> s_ub; |
495 | ||
c9d40b1f | 496 | DNSZoneRecord dr; |
497 | vector<DNSZoneRecord> ret; | |
18cb84d3 RG |
498 | { |
499 | auto ub = s_ub.lock(); | |
500 | ub->lookup(QType(qtype), name, zoneid); | |
501 | while (ub->get(dr)) { | |
502 | ret.push_back(dr); | |
503 | } | |
c9d40b1f | 504 | } |
505 | return ret; | |
506 | } | |
975f0839 | 507 | |
2fe7bbf8 | 508 | static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue) |
5ca4872a | 509 | { |
2fe7bbf8 | 510 | string selector=defaultValue; |
5ca4872a | 511 | if(options) { |
2fe7bbf8 OV |
512 | if(options->count(name)) |
513 | selector=options->find(name)->second; | |
5ca4872a | 514 | } |
2fe7bbf8 OV |
515 | return selector; |
516 | } | |
517 | ||
518 | static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates) | |
519 | { | |
520 | vector<ComboAddress> ret; | |
5ca4872a | 521 | |
707cedf3 | 522 | if(selector=="all") |
2fe7bbf8 OV |
523 | return candidates; |
524 | else if(selector=="random") | |
f52e47eb | 525 | ret.emplace_back(pickRandom<ComboAddress>(candidates)); |
1bc56192 | 526 | else if(selector=="pickclosest") |
2fe7bbf8 | 527 | ret.emplace_back(pickclosest(bestwho, candidates)); |
5ca4872a | 528 | else if(selector=="hashed") |
f52e47eb | 529 | ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates)); |
2fe7bbf8 OV |
530 | else { |
531 | g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl; | |
f52e47eb | 532 | ret.emplace_back(pickRandom<ComboAddress>(candidates)); |
2fe7bbf8 OV |
533 | } |
534 | ||
535 | return ret; | |
536 | } | |
5ca4872a | 537 | |
60f9d9a2 | 538 | static vector<string> convComboAddressListToString(const vector<ComboAddress>& items) |
2fe7bbf8 | 539 | { |
60f9d9a2 | 540 | vector<string> result; |
541 | result.reserve(items.size()); | |
c9768b80 | 542 | |
60f9d9a2 | 543 | for (const auto& item : items) { |
544 | result.emplace_back(item.toString()); | |
c9768b80 CHB |
545 | } |
546 | ||
60f9d9a2 | 547 | return result; |
5ca4872a | 548 | } |
549 | ||
6c8714d2 | 550 | static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0) |
1bc56192 | 551 | { |
60f9d9a2 | 552 | vector<ComboAddress> result; |
553 | result.reserve(items.size()); | |
1bc56192 | 554 | |
60f9d9a2 | 555 | for(const auto& item : items) { |
6c8714d2 | 556 | result.emplace_back(ComboAddress(item.second, port)); |
c9768b80 | 557 | } |
1bc56192 | 558 | |
60f9d9a2 | 559 | return result; |
1bc56192 CHB |
560 | } |
561 | ||
04c53783 CHB |
562 | /** |
563 | * Reads and unify single or multiple sets of ips : | |
564 | * - {'192.0.2.1', '192.0.2.2'} | |
565 | * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}} | |
566 | */ | |
567 | ||
568 | static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0) | |
569 | { | |
570 | vector<vector<ComboAddress>> candidates; | |
571 | ||
572 | if(auto simple = boost::get<iplist_t>(&items)) { | |
573 | vector<ComboAddress> unit = convComboAddressList(*simple, port); | |
574 | candidates.push_back(unit); | |
575 | } else { | |
576 | auto units = boost::get<ipunitlist_t>(items); | |
577 | for(const auto& u : units) { | |
578 | vector<ComboAddress> unit = convComboAddressList(u.second, port); | |
579 | candidates.push_back(unit); | |
580 | } | |
581 | } | |
582 | return candidates; | |
583 | } | |
584 | ||
60f9d9a2 | 585 | static vector<string> convStringList(const iplist_t& items) |
1bc56192 | 586 | { |
60f9d9a2 | 587 | vector<string> result; |
588 | result.reserve(items.size()); | |
1bc56192 | 589 | |
60f9d9a2 | 590 | for(const auto& item : items) { |
591 | result.emplace_back(item.second); | |
c9768b80 | 592 | } |
1bc56192 | 593 | |
60f9d9a2 | 594 | return result; |
595 | } | |
596 | ||
60f9d9a2 | 597 | static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items) |
598 | { | |
599 | vector<pair<int,string> > result; | |
600 | result.reserve(items.size()); | |
601 | ||
602 | for(const auto& item : items) { | |
603 | result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2)); | |
604 | } | |
605 | ||
606 | return result; | |
1bc56192 CHB |
607 | } |
608 | ||
32829819 | 609 | bool g_LuaRecordSharedState; |
fd1bdfb3 | 610 | |
c13af350 | 611 | typedef struct AuthLuaRecordContext |
b7edebf8 | 612 | { |
c13af350 CHB |
613 | ComboAddress bestwho; |
614 | DNSName qname; | |
615 | DNSName zone; | |
616 | int zoneid; | |
617 | } lua_record_ctx_t; | |
1bc56192 | 618 | |
c13af350 | 619 | static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx; |
1bc56192 | 620 | |
04c53783 CHB |
621 | static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options, std::function<bool(const ComboAddress&, const opts_t&)> upcheckf, uint16_t port = 0) { |
622 | vector<vector<ComboAddress> > candidates; | |
623 | opts_t opts; | |
624 | if(options) | |
625 | opts = *options; | |
626 | ||
627 | candidates = convMultiComboAddressList(ips, port); | |
628 | ||
629 | for(const auto& unit : candidates) { | |
630 | vector<ComboAddress> available; | |
631 | for(const auto& c : unit) { | |
632 | if (upcheckf(c, opts)) { | |
633 | available.push_back(c); | |
634 | } | |
635 | } | |
636 | if(!available.empty()) { | |
637 | vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available); | |
638 | return convComboAddressListToString(res); | |
639 | } | |
640 | } | |
641 | ||
642 | // All units down, apply backupSelector on all candidates | |
643 | vector<ComboAddress> ret{}; | |
644 | for(const auto& unit : candidates) { | |
645 | ret.insert(ret.end(), unit.begin(), unit.end()); | |
646 | } | |
647 | ||
648 | vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret); | |
649 | return convComboAddressListToString(res); | |
650 | } | |
651 | ||
d43d0b93 | 652 | static void setupLuaRecords(LuaContext& lua) |
c13af350 | 653 | { |
c13af350 | 654 | lua.writeFunction("latlon", []() { |
b17434c6 | 655 | double lat = 0, lon = 0; |
c13af350 | 656 | getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon); |
0540d223 | 657 | return std::to_string(lat)+" "+std::to_string(lon); |
658 | }); | |
c13af350 | 659 | lua.writeFunction("latlonloc", []() { |
0540d223 | 660 | string loc; |
c13af350 | 661 | getLatLon(s_lua_record_ctx->bestwho.toString(), loc); |
0540d223 | 662 | return loc; |
b7edebf8 | 663 | }); |
c13af350 | 664 | lua.writeFunction("closestMagic", []() { |
b7edebf8 | 665 | vector<ComboAddress> candidates; |
cc5c4f6b | 666 | // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org |
c13af350 | 667 | for(auto l : s_lua_record_ctx->qname.getRawLabels()) { |
b7edebf8 | 668 | boost::replace_all(l, "-", "."); |
b7edebf8 | 669 | try { |
670 | candidates.emplace_back(l); | |
6d7e1fd3 | 671 | } catch (const PDNSException& e) { |
cc5c4f6b CHB |
672 | // no need to continue as we most likely reached the end of the ip list |
673 | break ; | |
b7edebf8 | 674 | } |
675 | } | |
c13af350 | 676 | return pickclosest(s_lua_record_ctx->bestwho, candidates).toString(); |
b7edebf8 | 677 | }); |
c13af350 CHB |
678 | lua.writeFunction("latlonMagic", [](){ |
679 | auto labels= s_lua_record_ctx->qname.getRawLabels(); | |
7fedb52a | 680 | if(labels.size()<4) |
681 | return std::string("unknown"); | |
b17434c6 | 682 | double lat = 0, lon = 0; |
7fedb52a | 683 | getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon); |
684 | return std::to_string(lat)+" "+std::to_string(lon); | |
685 | }); | |
c9d40b1f | 686 | |
1bc56192 | 687 | |
54c20d39 | 688 | lua.writeFunction("createReverse", [](string format, boost::optional<std::unordered_map<string,string>> e){ |
c9d40b1f | 689 | try { |
c13af350 CHB |
690 | auto labels = s_lua_record_ctx->qname.getRawLabels(); |
691 | if(labels.size()<4) | |
692 | return std::string("unknown"); | |
bb85386d | 693 | |
c13af350 | 694 | vector<ComboAddress> candidates; |
bb85386d | 695 | |
c13af350 | 696 | // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa |
223bfcad | 697 | // e["1.2.3.4"]="bert.powerdns.com" then provides an exception |
c13af350 CHB |
698 | if(e) { |
699 | ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0); | |
700 | const auto& uom = *e; | |
701 | for(const auto& c : uom) | |
702 | if(ComboAddress(c.first, 0) == req) | |
703 | return c.second; | |
704 | } | |
54c20d39 | 705 | boost::format fmt(format); |
c13af350 CHB |
706 | fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) ); |
707 | fmt % labels[3] % labels[2] % labels[1] % labels[0]; | |
bb85386d | 708 | |
c13af350 | 709 | fmt % (labels[3]+"-"+labels[2]+"-"+labels[1]+"-"+labels[0]); |
c9d40b1f | 710 | |
c13af350 CHB |
711 | boost::format fmt2("%02x%02x%02x%02x"); |
712 | for(int i=3; i>=0; --i) | |
713 | fmt2 % atoi(labels[i].c_str()); | |
c9d40b1f | 714 | |
c13af350 | 715 | fmt % (fmt2.str()); |
c9d40b1f | 716 | |
c13af350 | 717 | return fmt.str(); |
c9d40b1f | 718 | } |
af6bf47c OM |
719 | catch(std::exception& ex) { |
720 | g_log<<Logger::Error<<"error: "<<ex.what()<<endl; | |
c9d40b1f | 721 | } |
722 | return std::string("error"); | |
723 | }); | |
c13af350 | 724 | lua.writeFunction("createForward", []() { |
448e7a2d | 725 | static string allZerosIP("0.0.0.0"); |
c13af350 | 726 | DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone); |
448e7a2d PL |
727 | // parts is something like ["1", "2", "3", "4", "static"] or |
728 | // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...] | |
c9d40b1f | 729 | auto parts = rel.getRawLabels(); |
448e7a2d PL |
730 | // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply... |
731 | if(parts.size()>=4) { | |
732 | try { | |
733 | ComboAddress ca(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]); | |
734 | return ca.toString(); | |
735 | } catch (const PDNSException &e) { | |
736 | return allZerosIP; | |
737 | } | |
738 | } else if (parts.size() >= 1) { | |
c9d40b1f | 739 | // either hex string, or 12-13-14-15 |
448e7a2d PL |
740 | vector<string> ip_parts; |
741 | stringtok(ip_parts, parts[0], "-"); | |
df6ad4ee | 742 | unsigned int x1, x2, x3, x4; |
448e7a2d PL |
743 | if (ip_parts.size() >= 4) { |
744 | // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4) | |
745 | string ret; | |
746 | for (size_t n=4; n > 0; n--) { | |
747 | auto octet = ip_parts[ip_parts.size() - n]; | |
748 | try { | |
749 | auto octetVal = std::stol(octet); | |
750 | if (octetVal >= 0 && octetVal <= 255) { | |
751 | ret += ip_parts.at(ip_parts.size() - n) + "."; | |
752 | } else { | |
753 | return allZerosIP; | |
754 | } | |
755 | } catch (const std::exception &e) { | |
756 | return allZerosIP; | |
757 | } | |
758 | } | |
759 | ret.resize(ret.size() - 1); // remove trailing dot after last octet | |
760 | return ret; | |
9af5ff11 | 761 | } else if(parts[0].length() >= 8 && sscanf(parts[0].c_str()+(parts[0].length()-8), "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) { |
c9d40b1f | 762 | return std::to_string(x1)+"."+std::to_string(x2)+"."+std::to_string(x3)+"."+std::to_string(x4); |
763 | } | |
c9d40b1f | 764 | } |
448e7a2d | 765 | return allZerosIP; |
c9d40b1f | 766 | }); |
767 | ||
c13af350 CHB |
768 | lua.writeFunction("createForward6", []() { |
769 | DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone); | |
c9d40b1f | 770 | auto parts = rel.getRawLabels(); |
771 | if(parts.size()==8) { | |
772 | string tot; | |
773 | for(int i=0; i<8; ++i) { | |
774 | if(i) | |
775 | tot.append(1,':'); | |
776 | tot+=parts[i]; | |
777 | } | |
778 | ComboAddress ca(tot); | |
779 | return ca.toString(); | |
780 | } | |
781 | else if(parts.size()==1) { | |
ada6063a PD |
782 | if (parts[0].find('-') != std::string::npos) { |
783 | boost::replace_all(parts[0],"-",":"); | |
784 | ComboAddress ca(parts[0]); | |
785 | return ca.toString(); | |
786 | } else { | |
787 | if (parts[0].size() >= 32) { | |
788 | auto ippart = parts[0].substr(parts[0].size()-32); | |
789 | auto fulladdress = | |
790 | ippart.substr(0, 4) + ":" + | |
791 | ippart.substr(4, 4) + ":" + | |
792 | ippart.substr(8, 4) + ":" + | |
793 | ippart.substr(12, 4) + ":" + | |
794 | ippart.substr(16, 4) + ":" + | |
795 | ippart.substr(20, 4) + ":" + | |
796 | ippart.substr(24, 4) + ":" + | |
797 | ippart.substr(28, 4); | |
798 | ||
799 | ComboAddress ca(fulladdress); | |
800 | return ca.toString(); | |
801 | } | |
802 | } | |
c9d40b1f | 803 | } |
804 | ||
805 | return std::string("::"); | |
806 | }); | |
54c20d39 | 807 | lua.writeFunction("createReverse6", [](string format, boost::optional<std::unordered_map<string,string>> e){ |
c9d40b1f | 808 | vector<ComboAddress> candidates; |
809 | ||
810 | try { | |
c13af350 | 811 | auto labels= s_lua_record_ctx->qname.getRawLabels(); |
c9d40b1f | 812 | if(labels.size()<32) |
813 | return std::string("unknown"); | |
54c20d39 | 814 | boost::format fmt(format); |
c9d40b1f | 815 | fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) ); |
1bc56192 | 816 | |
c9d40b1f | 817 | |
818 | string together; | |
819 | vector<string> quads; | |
820 | for(int i=0; i<8; ++i) { | |
821 | if(i) | |
822 | together+=":"; | |
a3739f30 | 823 | string lquad; |
c9d40b1f | 824 | for(int j=0; j <4; ++j) { |
a3739f30 | 825 | lquad.append(1, labels[31-i*4-j][0]); |
c9d40b1f | 826 | together += labels[31-i*4-j][0]; |
827 | } | |
a3739f30 | 828 | quads.push_back(lquad); |
c9d40b1f | 829 | } |
830 | ComboAddress ip6(together,0); | |
831 | ||
832 | if(e) { | |
833 | auto& addrs=*e; | |
834 | for(const auto& addr: addrs) { | |
835 | // this makes sure we catch all forms of the address | |
836 | if(ComboAddress(addr.first,0)==ip6) | |
837 | return addr.second; | |
838 | } | |
839 | } | |
1bc56192 | 840 | |
c9d40b1f | 841 | string dashed=ip6.toString(); |
842 | boost::replace_all(dashed, ":", "-"); | |
1bc56192 | 843 | |
c9d40b1f | 844 | for(int i=31; i>=0; --i) |
845 | fmt % labels[i]; | |
846 | fmt % dashed; | |
847 | ||
a3739f30 OM |
848 | for(const auto& lquad : quads) |
849 | fmt % lquad; | |
1bc56192 | 850 | |
c9d40b1f | 851 | return fmt.str(); |
852 | } | |
af6bf47c | 853 | catch(std::exception& ex) { |
e7929442 | 854 | g_log<<Logger::Error<<"LUA Record exception: "<<ex.what()<<endl; |
c9d40b1f | 855 | } |
af6bf47c OM |
856 | catch(PDNSException& ex) { |
857 | g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl; | |
c9d40b1f | 858 | } |
859 | return std::string("unknown"); | |
860 | }); | |
861 | ||
223bfcad PD |
862 | lua.writeFunction("filterForward", [](string address, NetmaskGroup& nmg, boost::optional<string> fallback) { |
863 | ComboAddress ca(address); | |
864 | ||
865 | if (nmg.match(ComboAddress(address))) { | |
866 | return address; | |
867 | } else { | |
868 | if (fallback) { | |
869 | return *fallback; | |
870 | } | |
871 | ||
872 | if (ca.isIPv4()) { | |
873 | return string("0.0.0.0"); | |
874 | } else { | |
875 | return string("::"); | |
876 | } | |
877 | } | |
878 | }); | |
879 | ||
1bc56192 CHB |
880 | /* |
881 | * Simplistic test to see if an IP address listens on a certain port | |
882 | * Will return a single IP address from the set of available IP addresses. If | |
883 | * no IP address is available, will return a random element of the set of | |
ef2ea4bf | 884 | * addresses supplied for testing. |
1bc56192 CHB |
885 | * |
886 | * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })" | |
887 | */ | |
6c8714d2 | 888 | lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, const boost::optional<std::unordered_map<string,string>> options) { |
6c8714d2 CHB |
889 | if (port < 0) { |
890 | port = 0; | |
891 | } | |
892 | if (port > std::numeric_limits<uint16_t>::max()) { | |
893 | port = std::numeric_limits<uint16_t>::max(); | |
894 | } | |
6c8714d2 | 895 | |
04c53783 CHB |
896 | auto checker = [](const ComboAddress& addr, const opts_t& opts) { |
897 | return g_up.isUp(addr, opts); | |
898 | }; | |
de5fb851 | 899 | return genericIfUp(ips, std::move(options), checker, port); |
1bc56192 | 900 | }); |
b7edebf8 | 901 | |
95d0df69 PD |
902 | lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) { |
903 | vector<ComboAddress> candidates; | |
904 | opts_t opts; | |
905 | if(options) | |
906 | opts = *options; | |
907 | ||
908 | ComboAddress ca_unspec; | |
909 | ca_unspec.sin4.sin_family=AF_UNSPEC; | |
910 | ||
911 | // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } } | |
912 | for (const auto& [count, unitmap] : ipurls) { | |
913 | // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } | |
914 | vector<ComboAddress> available; | |
915 | ||
916 | for (const auto& [ipStr, url] : unitmap) { | |
917 | // unit: ["192.0.2.1"] = "https://example.com" | |
918 | ComboAddress ip(ipStr); | |
919 | candidates.push_back(ip); | |
920 | if (g_up.isUp(ca_unspec, url, opts)) { | |
921 | available.push_back(ip); | |
922 | } | |
923 | } | |
924 | if(!available.empty()) { | |
925 | vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available); | |
60f9d9a2 | 926 | return convComboAddressListToString(res); |
95d0df69 PD |
927 | } |
928 | } | |
929 | ||
930 | // All units down, apply backupSelector on all candidates | |
931 | vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates); | |
60f9d9a2 | 932 | return convComboAddressListToString(res); |
95d0df69 PD |
933 | }); |
934 | ||
c13af350 | 935 | lua.writeFunction("ifurlup", [](const std::string& url, |
1bc56192 CHB |
936 | const boost::variant<iplist_t, ipunitlist_t>& ips, |
937 | boost::optional<opts_t> options) { | |
b7edebf8 | 938 | |
04c53783 CHB |
939 | auto checker = [&url](const ComboAddress& addr, const opts_t& opts) { |
940 | return g_up.isUp(addr, url, opts); | |
941 | }; | |
942 | return genericIfUp(ips, options, checker); | |
2fe7bbf8 | 943 | }); |
1bc56192 CHB |
944 | /* |
945 | * Returns a random IP address from the supplied list | |
946 | * @example pickrandom({ '1.2.3.4', '5.4.3.2' })" | |
947 | */ | |
948 | lua.writeFunction("pickrandom", [](const iplist_t& ips) { | |
60f9d9a2 | 949 | vector<string> items = convStringList(ips); |
f52e47eb | 950 | return pickRandom<string>(items); |
b7edebf8 | 951 | }); |
952 | ||
60f9d9a2 | 953 | lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) { |
954 | vector<string> items = convStringList(ips); | |
f52e47eb | 955 | return pickRandomSample<string>(n, items); |
60f9d9a2 | 956 | }); |
b7edebf8 | 957 | |
60f9d9a2 | 958 | lua.writeFunction("pickhashed", [](const iplist_t& ips) { |
959 | vector<string> items = convStringList(ips); | |
f52e47eb | 960 | return pickHashed<string>(s_lua_record_ctx->bestwho, items); |
60f9d9a2 | 961 | }); |
1bc56192 CHB |
962 | /* |
963 | * Returns a random IP address from the supplied list, as weighted by the | |
964 | * various ``weight`` parameters | |
965 | * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} }) | |
966 | */ | |
967 | lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) { | |
60f9d9a2 | 968 | vector< pair<int, string> > items = convIntStringPairList(ips); |
f52e47eb | 969 | return pickWeightedRandom<string>(items); |
975f0839 | 970 | }); |
971 | ||
1bc56192 CHB |
972 | /* |
973 | * Based on the hash of `bestwho`, returns an IP address from the list | |
974 | * supplied, as weighted by the various `weight` parameters | |
975 | * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} }) | |
976 | */ | |
c13af350 | 977 | lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) { |
60f9d9a2 | 978 | vector< pair<int, string> > items; |
1bc56192 | 979 | |
60f9d9a2 | 980 | items.reserve(ips.size()); |
1bc56192 | 981 | for(auto& i : ips) |
60f9d9a2 | 982 | items.emplace_back(atoi(i.second[1].c_str()), i.second[2]); |
1bc56192 | 983 | |
f52e47eb | 984 | return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items); |
975f0839 | 985 | }); |
986 | ||
987 | ||
c13af350 | 988 | lua.writeFunction("pickclosest", [](const iplist_t& ips) { |
60f9d9a2 | 989 | vector<ComboAddress> conv = convComboAddressList(ips); |
1bc56192 | 990 | |
c13af350 | 991 | return pickclosest(s_lua_record_ctx->bestwho, conv).toString(); |
1bc56192 | 992 | |
b7edebf8 | 993 | }); |
994 | ||
c13af350 CHB |
995 | if (g_luaRecordExecLimit > 0) { |
996 | lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit)); | |
997 | } | |
1bc56192 | 998 | |
d73de874 | 999 | lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){ |
b7edebf8 | 1000 | throw std::runtime_error("Script took too long"); |
1001 | }); | |
b7edebf8 | 1002 | |
f9423419 | 1003 | lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) { |
1d56dac6 PD |
1004 | return getGeo(ip, attr); |
1005 | }); | |
1006 | ||
b7edebf8 | 1007 | typedef const boost::variant<string,vector<pair<int,string> > > combovar_t; |
60f9d9a2 | 1008 | |
1009 | lua.writeFunction("asnum", [](const combovar_t& asns) { | |
1010 | string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn); | |
1011 | return doCompare(asns, res, [](const std::string& a, const std::string& b) { | |
1012 | return !strcasecmp(a.c_str(), b.c_str()); | |
1013 | }); | |
1014 | }); | |
c13af350 CHB |
1015 | lua.writeFunction("continent", [](const combovar_t& continent) { |
1016 | string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent); | |
b7edebf8 | 1017 | return doCompare(continent, res, [](const std::string& a, const std::string& b) { |
1018 | return !strcasecmp(a.c_str(), b.c_str()); | |
1019 | }); | |
1020 | }); | |
60f9d9a2 | 1021 | lua.writeFunction("continentCode", []() { |
1022 | string unknown("unknown"); | |
1023 | string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent); | |
1024 | if ( res == unknown ) { | |
1025 | return std::string("--"); | |
1026 | } | |
1027 | return res; | |
1028 | }); | |
1029 | lua.writeFunction("country", [](const combovar_t& var) { | |
1030 | string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2); | |
1031 | return doCompare(var, res, [](const std::string& a, const std::string& b) { | |
b7edebf8 | 1032 | return !strcasecmp(a.c_str(), b.c_str()); |
1033 | }); | |
60f9d9a2 | 1034 | |
b7edebf8 | 1035 | }); |
60f9d9a2 | 1036 | lua.writeFunction("countryCode", []() { |
1037 | string unknown("unknown"); | |
c13af350 | 1038 | string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2); |
60f9d9a2 | 1039 | if ( res == unknown ) { |
1040 | return std::string("--"); | |
1041 | } | |
1042 | return res; | |
1043 | }); | |
1044 | lua.writeFunction("region", [](const combovar_t& var) { | |
1045 | string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region); | |
b7edebf8 | 1046 | return doCompare(var, res, [](const std::string& a, const std::string& b) { |
1047 | return !strcasecmp(a.c_str(), b.c_str()); | |
1048 | }); | |
1bc56192 | 1049 | |
b7edebf8 | 1050 | }); |
60f9d9a2 | 1051 | lua.writeFunction("regionCode", []() { |
1052 | string unknown("unknown"); | |
1053 | string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region); | |
1054 | if ( res == unknown ) { | |
1055 | return std::string("--"); | |
1056 | } | |
1057 | return res; | |
1058 | }); | |
c13af350 | 1059 | lua.writeFunction("netmask", [](const iplist_t& ips) { |
b7edebf8 | 1060 | for(const auto& i :ips) { |
1061 | Netmask nm(i.second); | |
c13af350 | 1062 | if(nm.match(s_lua_record_ctx->bestwho)) |
b7edebf8 | 1063 | return true; |
1064 | } | |
1065 | return false; | |
1066 | }); | |
b7edebf8 | 1067 | /* { |
1068 | { | |
1bc56192 | 1069 | {'192.168.0.0/16', '10.0.0.0/8'}, |
b7edebf8 | 1070 | {'192.168.20.20', '192.168.20.21'} |
1071 | }, | |
1072 | { | |
1073 | {'0.0.0.0/0'}, {'192.0.2.1'} | |
1074 | } | |
1075 | } | |
1bc56192 | 1076 | */ |
c13af350 | 1077 | lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) { |
b7edebf8 | 1078 | for(const auto& rule : in) { |
1079 | const auto& netmasks=rule.second[0].second; | |
1080 | const auto& destinations=rule.second[1].second; | |
1081 | for(const auto& nmpair : netmasks) { | |
1082 | Netmask nm(nmpair.second); | |
c13af350 | 1083 | if(nm.match(s_lua_record_ctx->bestwho)) { |
28beeb0f PD |
1084 | if (destinations.empty()) { |
1085 | throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm.toString() + ")"); | |
1086 | } | |
b51ef4f9 | 1087 | return destinations[dns_random(destinations.size())].second; |
b7edebf8 | 1088 | } |
1089 | } | |
1090 | } | |
1091 | return std::string(); | |
60f9d9a2 | 1092 | }); |
1bc56192 | 1093 | |
60f9d9a2 | 1094 | lua.writeFunction("all", [](const vector< pair<int,string> >& ips) { |
1095 | vector<string> result; | |
1096 | result.reserve(ips.size()); | |
bb85386d | 1097 | |
60f9d9a2 | 1098 | for(const auto& ip : ips) { |
1099 | result.emplace_back(ip.second); | |
1100 | } | |
1101 | if(result.empty()) { | |
1102 | throw std::invalid_argument("The IP list cannot be empty"); | |
1103 | } | |
1104 | return result; | |
1105 | }); | |
1bc56192 | 1106 | |
c13af350 | 1107 | lua.writeFunction("include", [&lua](string record) { |
388d3a43 | 1108 | DNSName rec; |
b7edebf8 | 1109 | try { |
388d3a43 PL |
1110 | rec = DNSName(record) + s_lua_record_ctx->zone; |
1111 | } catch (const std::exception &e){ | |
51a4e451 | 1112 | g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl; |
388d3a43 PL |
1113 | return; |
1114 | } | |
1115 | try { | |
1116 | vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zoneid); | |
c9d40b1f | 1117 | for(const auto& dr : drs) { |
b7edebf8 | 1118 | auto lr = getRR<LUARecordContent>(dr.dr); |
1119 | lua.executeCode(lr->getCode()); | |
1120 | } | |
25bcfaec | 1121 | } |
1122 | catch(std::exception& e) { | |
388d3a43 | 1123 | g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<rec<<": "<<e.what()<<endl; |
25bcfaec | 1124 | } |
b7edebf8 | 1125 | }); |
c13af350 CHB |
1126 | } |
1127 | ||
87f1cd7c | 1128 | 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 | 1129 | { |
87f1cd7c | 1130 | if(!LUA || // we don't have a Lua state yet |
c13af350 | 1131 | !g_LuaRecordSharedState) { // or we want a new one even if we had one |
87f1cd7c PD |
1132 | LUA = make_unique<AuthLua4>(); |
1133 | setupLuaRecords(*LUA->getLua()); | |
c13af350 CHB |
1134 | } |
1135 | ||
1136 | std::vector<shared_ptr<DNSRecordContent>> ret; | |
1137 | ||
87f1cd7c | 1138 | LuaContext& lua = *LUA->getLua(); |
c13af350 | 1139 | |
2bbc9eb0 | 1140 | s_lua_record_ctx = std::make_unique<lua_record_ctx_t>(); |
c13af350 CHB |
1141 | s_lua_record_ctx->qname = query; |
1142 | s_lua_record_ctx->zone = zone; | |
1143 | s_lua_record_ctx->zoneid = zoneid; | |
bb85386d | 1144 | |
c13af350 CHB |
1145 | lua.writeVariable("qname", query); |
1146 | lua.writeVariable("zone", zone); | |
1147 | lua.writeVariable("zoneid", zoneid); | |
4172a5b2 | 1148 | lua.writeVariable("who", dnsp.getInnerRemote()); |
c13af350 CHB |
1149 | lua.writeVariable("dh", (dnsheader*)&dnsp.d); |
1150 | lua.writeVariable("dnssecOK", dnsp.d_dnssecOk); | |
1151 | lua.writeVariable("tcp", dnsp.d_tcp); | |
1152 | lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit); | |
1153 | if(dnsp.hasEDNSSubnet()) { | |
1154 | lua.writeVariable("ecswho", dnsp.getRealRemote()); | |
1155 | s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork(); | |
1156 | } | |
1157 | else { | |
1158 | lua.writeVariable("ecswho", nullptr); | |
4172a5b2 | 1159 | s_lua_record_ctx->bestwho = dnsp.getInnerRemote(); |
c13af350 CHB |
1160 | } |
1161 | lua.writeVariable("bestwho", s_lua_record_ctx->bestwho); | |
b7edebf8 | 1162 | |
b7edebf8 | 1163 | try { |
1164 | string actual; | |
1165 | if(!code.empty() && code[0]!=';') | |
6180eab4 PD |
1166 | actual = "return " + code; |
1167 | else | |
1168 | actual = code.substr(1); | |
c9d40b1f | 1169 | |
b7edebf8 | 1170 | auto content=lua.executeCode<boost::variant<string, vector<pair<int, string> > > >(actual); |
c9d40b1f | 1171 | |
b7edebf8 | 1172 | vector<string> contents; |
1173 | if(auto str = boost::get<string>(&content)) | |
1174 | contents.push_back(*str); | |
1175 | else | |
1176 | for(const auto& c : boost::get<vector<pair<int,string>>>(content)) | |
1177 | contents.push_back(c.second); | |
1bc56192 | 1178 | |
690b86b7 | 1179 | for(const auto& content_it: contents) { |
b7edebf8 | 1180 | if(qtype==QType::TXT) |
d525b58b | 1181 | ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"')); |
b7edebf8 | 1182 | else |
d525b58b | 1183 | ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it)); |
b7edebf8 | 1184 | } |
1bc56192 | 1185 | } catch(std::exception &e) { |
d5fcd583 | 1186 | g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what(); |
3217f4ea PD |
1187 | try { |
1188 | std::rethrow_if_nested(e); | |
1189 | g_log<<endl; | |
1190 | } catch(const std::exception& ne) { | |
1191 | g_log << ": " << ne.what() << std::endl; | |
1192 | } | |
1193 | catch(const PDNSException& ne) { | |
1194 | g_log << ": " << ne.reason << std::endl; | |
1195 | } | |
1bc56192 | 1196 | throw ; |
b7edebf8 | 1197 | } |
1198 | ||
1199 | return ret; | |
1200 | } |