]>
Commit | Line | Data |
---|---|---|
192e15fc | 1 | #include "version.hh" |
b7edebf8 | 2 | #include "ext/luawrapper/include/LuaContext.hpp" |
3 | #include "lua-auth4.hh" | |
4 | #include <thread> | |
5 | #include "sstuff.hh" | |
6 | #include <mutex> | |
7 | #include "minicurl.hh" | |
8 | #include "ueberbackend.hh" | |
975f0839 | 9 | #include <boost/format.hpp> |
8900e2e3 | 10 | #include "dnsrecords.hh" |
45d4e670 | 11 | #include "dns_random.hh" |
25bcfaec | 12 | |
f9423419 | 13 | #include "../modules/geoipbackend/geoipinterface.hh" // only for the enum |
0540d223 | 14 | |
15 | /* to do: | |
25bcfaec | 16 | block AXFR unless TSIG, or override |
69ec43c3 | 17 | |
18 | investigate IPv6 | |
19 | ||
20 | check the wildcard 'no cache' stuff, we may get it wrong | |
21 | ||
22 | ponder ECS scopemask setting | |
23 | ||
24 | ponder netmask tree from file for huge number of netmasks | |
25bcfaec | 25 | |
0540d223 | 26 | unify ifupurl/ifupport |
69ec43c3 | 27 | add attribute for certificate check |
0540d223 | 28 | add list of current monitors |
29 | expire them too? | |
c9d40b1f | 30 | |
31 | pool of UeberBackends? | |
1bc56192 CHB |
32 | |
33 | Pool checks ? | |
0540d223 | 34 | */ |
35 | ||
af68014f CHB |
36 | extern int g_luaRecordExecLimit; |
37 | ||
1bc56192 CHB |
38 | using iplist_t = vector<pair<int, string> >; |
39 | using wiplist_t = std::unordered_map<int, string>; | |
40 | using ipunitlist_t = vector<pair<int, iplist_t> >; | |
41 | using opts_t = std::unordered_map<string,string>; | |
42 | ||
b7edebf8 | 43 | class IsUpOracle |
44 | { | |
45 | private: | |
b7edebf8 | 46 | struct CheckDesc |
47 | { | |
48 | ComboAddress rem; | |
49 | string url; | |
50 | opts_t opts; | |
51 | bool operator<(const CheckDesc& rhs) const | |
52 | { | |
25bcfaec | 53 | std::map<string,string> oopts, rhsoopts; |
54 | for(const auto& m : opts) | |
55 | oopts[m.first]=m.second; | |
56 | for(const auto& m : rhs.opts) | |
57 | rhsoopts[m.first]=m.second; | |
1bc56192 | 58 | |
25bcfaec | 59 | return std::make_tuple(rem, url, oopts) < |
60 | std::make_tuple(rhs.rem, rhs.url, rhsoopts); | |
b7edebf8 | 61 | } |
62 | }; | |
63 | public: | |
1bc56192 CHB |
64 | bool isUp(const ComboAddress& remote, const opts_t& opts); |
65 | bool isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts); | |
25bcfaec | 66 | bool isUp(const CheckDesc& cd); |
1bc56192 | 67 | |
b7edebf8 | 68 | private: |
1bc56192 CHB |
69 | void checkURLThread(ComboAddress rem, std::string url, const opts_t& opts); |
70 | void checkTCPThread(ComboAddress rem, const opts_t& opts); | |
b7edebf8 | 71 | |
72 | struct Checker | |
73 | { | |
df6ad4ee | 74 | std::thread thr; |
b7edebf8 | 75 | bool status; |
76 | }; | |
77 | ||
78 | typedef map<CheckDesc, Checker> statuses_t; | |
79 | statuses_t d_statuses; | |
1bc56192 | 80 | |
b7edebf8 | 81 | std::mutex d_mutex; |
82 | ||
1bc56192 | 83 | void setStatus(const CheckDesc& cd, bool status) |
b7edebf8 | 84 | { |
85 | std::lock_guard<std::mutex> l(d_mutex); | |
86 | d_statuses[cd].status=status; | |
87 | } | |
88 | ||
1bc56192 | 89 | void setDown(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t()) |
b7edebf8 | 90 | { |
91 | CheckDesc cd{rem, url, opts}; | |
92 | setStatus(cd, false); | |
93 | } | |
94 | ||
1bc56192 | 95 | void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t()) |
b7edebf8 | 96 | { |
97 | CheckDesc cd{rem, url, opts}; | |
1bc56192 | 98 | |
b7edebf8 | 99 | setStatus(cd, true); |
100 | } | |
101 | ||
102 | void setDown(const CheckDesc& cd) | |
103 | { | |
104 | setStatus(cd, false); | |
105 | } | |
106 | ||
107 | void setUp(const CheckDesc& cd) | |
108 | { | |
109 | setStatus(cd, true); | |
110 | } | |
111 | ||
1bc56192 | 112 | bool upStatus(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t()) |
b7edebf8 | 113 | { |
114 | CheckDesc cd{rem, url, opts}; | |
115 | std::lock_guard<std::mutex> l(d_mutex); | |
116 | return d_statuses[cd].status; | |
117 | } | |
b7edebf8 | 118 | }; |
119 | ||
25bcfaec | 120 | bool IsUpOracle::isUp(const CheckDesc& cd) |
b7edebf8 | 121 | { |
122 | std::lock_guard<std::mutex> l(d_mutex); | |
b7edebf8 | 123 | auto iter = d_statuses.find(cd); |
124 | if(iter == d_statuses.end()) { | |
df6ad4ee | 125 | d_statuses[cd]=Checker{std::thread(&IsUpOracle::checkTCPThread, this, cd.rem, cd.opts), false}; |
b7edebf8 | 126 | return false; |
127 | } | |
128 | return iter->second.status; | |
25bcfaec | 129 | |
130 | } | |
131 | ||
1bc56192 | 132 | bool IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts) |
25bcfaec | 133 | { |
134 | CheckDesc cd{remote, "", opts}; | |
135 | return isUp(cd); | |
b7edebf8 | 136 | } |
137 | ||
1bc56192 | 138 | bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts) |
b7edebf8 | 139 | { |
140 | CheckDesc cd{remote, url, opts}; | |
141 | std::lock_guard<std::mutex> l(d_mutex); | |
142 | auto iter = d_statuses.find(cd); | |
143 | if(iter == d_statuses.end()) { | |
f5f3d7f5 | 144 | // g_log<<Logger::Warning<<"Launching HTTP(s) status checker for "<<remote.toStringWithPort()<<" and URL "<<url<<endl; |
df6ad4ee | 145 | d_statuses[cd]=Checker{std::thread(&IsUpOracle::checkURLThread, this, remote, url, opts), false}; |
b7edebf8 | 146 | return false; |
147 | } | |
1bc56192 | 148 | |
b7edebf8 | 149 | return iter->second.status; |
150 | } | |
151 | ||
1bc56192 | 152 | void IsUpOracle::checkTCPThread(ComboAddress rem, const opts_t& opts) |
25bcfaec | 153 | { |
154 | CheckDesc cd{rem, "", opts}; | |
155 | setDown(cd); | |
156 | for(bool first=true;;first=false) { | |
157 | try { | |
158 | Socket s(rem.sin4.sin_family, SOCK_STREAM); | |
25bcfaec | 159 | ComboAddress src; |
1bc56192 | 160 | s.setNonBlocking(); |
25bcfaec | 161 | if(opts.count("source")) { |
1bc56192 | 162 | src=ComboAddress(opts.at("source")); |
25bcfaec | 163 | s.bind(src); |
164 | } | |
165 | s.connect(rem, 1); | |
166 | if(!isUp(cd)) { | |
f5f3d7f5 | 167 | g_log<<Logger::Warning<<"Lua record monitoring declaring TCP/IP "<<rem.toStringWithPort()<<" "; |
25bcfaec | 168 | if(opts.count("source")) |
f5f3d7f5 CHB |
169 | g_log<<"(source "<<src.toString()<<") "; |
170 | g_log<<"UP!"<<endl; | |
25bcfaec | 171 | } |
172 | setUp(cd); | |
173 | } | |
174 | catch(NetworkError& ne) { | |
175 | if(isUp(rem, opts) || first) | |
f5f3d7f5 | 176 | g_log<<Logger::Warning<<"Lua record monitoring declaring TCP/IP "<<rem.toStringWithPort()<<" DOWN: "<<ne.what()<<endl; |
25bcfaec | 177 | setDown(cd); |
178 | } | |
179 | sleep(1); | |
180 | } | |
181 | } | |
182 | ||
7d5bf0bb | 183 | |
1bc56192 | 184 | void IsUpOracle::checkURLThread(ComboAddress rem, std::string url, const opts_t& opts) |
b7edebf8 | 185 | { |
186 | setDown(rem, url, opts); | |
187 | for(bool first=true;;first=false) { | |
188 | try { | |
adf5f025 | 189 | string useragent = productName(); |
192e15fc PL |
190 | if (opts.count("useragent")) { |
191 | useragent = opts.at("useragent"); | |
192 | } | |
193 | MiniCurl mc(useragent); | |
25bcfaec | 194 | |
195 | string content; | |
196 | if(opts.count("source")) { | |
1bc56192 | 197 | ComboAddress src(opts.at("source")); |
25bcfaec | 198 | content=mc.getURL(url, &rem, &src); |
199 | } | |
1bc56192 | 200 | else { |
25bcfaec | 201 | content=mc.getURL(url, &rem); |
1bc56192 CHB |
202 | } |
203 | if(opts.count("stringmatch") && content.find(opts.at("stringmatch")) == string::npos) { | |
204 | throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % opts.at("stringmatch"))); | |
b7edebf8 | 205 | } |
5ca4872a | 206 | if(!upStatus(rem,url,opts)) |
f5f3d7f5 | 207 | g_log<<Logger::Warning<<"LUA record monitoring declaring "<<rem.toString()<<" UP for URL "<<url<<"!"<<endl; |
25bcfaec | 208 | setUp(rem, url,opts); |
b7edebf8 | 209 | } |
210 | catch(std::exception& ne) { | |
211 | if(upStatus(rem,url,opts) || first) | |
f5f3d7f5 | 212 | g_log<<Logger::Warning<<"LUA record monitoring declaring "<<rem.toString()<<" DOWN for URL "<<url<<", error: "<<ne.what()<<endl; |
25bcfaec | 213 | setDown(rem,url,opts); |
b7edebf8 | 214 | } |
b7edebf8 | 215 | sleep(5); |
216 | } | |
217 | } | |
218 | ||
5941d59a | 219 | |
7d5bf0bb | 220 | IsUpOracle g_up; |
b7edebf8 | 221 | namespace { |
222 | template<typename T, typename C> | |
223 | bool doCompare(const T& var, const std::string& res, const C& cmp) | |
224 | { | |
1bc56192 | 225 | if(auto country = boost::get<string>(&var)) |
b7edebf8 | 226 | return cmp(*country, res); |
227 | ||
228 | auto countries=boost::get<vector<pair<int,string> > >(&var); | |
229 | for(const auto& country : *countries) { | |
230 | if(cmp(country.second, res)) | |
231 | return true; | |
232 | } | |
233 | return false; | |
234 | } | |
235 | } | |
4a913a28 | 236 | |
237 | ||
f9423419 | 238 | std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa) |
b7edebf8 | 239 | { |
03be9ab5 | 240 | static bool initialized; |
25bcfaec | 241 | extern std::function<std::string(const std::string& ip, int)> g_getGeo; |
924341d2 | 242 | if(!g_getGeo) { |
243 | if(!initialized) { | |
f5f3d7f5 | 244 | g_log<<Logger::Error<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl; |
924341d2 | 245 | initialized=true; |
246 | } | |
247 | return "unknown"; | |
248 | } | |
249 | else | |
25bcfaec | 250 | return g_getGeo(ip, (int)qa); |
4a913a28 | 251 | } |
252 | ||
5ca4872a | 253 | static ComboAddress pickrandom(const vector<ComboAddress>& ips) |
4a913a28 | 254 | { |
6d7e1fd3 CHB |
255 | if (ips.empty()) { |
256 | throw std::invalid_argument("The IP list cannot be empty"); | |
257 | } | |
45d4e670 | 258 | return ips[dns_random(ips.size())]; |
4a913a28 | 259 | } |
260 | ||
5ca4872a | 261 | static ComboAddress hashed(const ComboAddress& who, const vector<ComboAddress>& ips) |
4a913a28 | 262 | { |
6d7e1fd3 CHB |
263 | if (ips.empty()) { |
264 | throw std::invalid_argument("The IP list cannot be empty"); | |
265 | } | |
4a913a28 | 266 | ComboAddress::addressOnlyHash aoh; |
267 | return ips[aoh(who) % ips.size()]; | |
b7edebf8 | 268 | } |
269 | ||
4a913a28 | 270 | |
1bc56192 | 271 | static ComboAddress pickwrandom(const vector<pair<int,ComboAddress> >& wips) |
975f0839 | 272 | { |
6d7e1fd3 CHB |
273 | if (wips.empty()) { |
274 | throw std::invalid_argument("The IP list cannot be empty"); | |
275 | } | |
975f0839 | 276 | int sum=0; |
277 | vector<pair<int, ComboAddress> > pick; | |
278 | for(auto& i : wips) { | |
279 | sum += i.first; | |
280 | pick.push_back({sum, i.second}); | |
281 | } | |
45d4e670 | 282 | int r = dns_random(sum); |
975f0839 | 283 | auto p = upper_bound(pick.begin(), pick.end(),r, [](int r, const decltype(pick)::value_type& a) { return r < a.first;}); |
284 | return p->second; | |
285 | } | |
286 | ||
1bc56192 | 287 | static ComboAddress pickwhashed(const ComboAddress& bestwho, vector<pair<int,ComboAddress> >& wips) |
975f0839 | 288 | { |
6d7e1fd3 CHB |
289 | if (wips.empty()) { |
290 | return ComboAddress(); | |
291 | } | |
975f0839 | 292 | int sum=0; |
293 | vector<pair<int, ComboAddress> > pick; | |
294 | for(auto& i : wips) { | |
295 | sum += i.first; | |
296 | pick.push_back({sum, i.second}); | |
297 | } | |
298 | ComboAddress::addressOnlyHash aoh; | |
299 | int r = aoh(bestwho) % sum; | |
300 | auto p = upper_bound(pick.begin(), pick.end(),r, [](int r, const decltype(pick)::value_type& a) { return r < a.first;}); | |
301 | return p->second; | |
302 | } | |
303 | ||
b7edebf8 | 304 | static bool getLatLon(const std::string& ip, double& lat, double& lon) |
305 | { | |
f9423419 | 306 | string inp = getGeo(ip, GeoIPInterface::Location); |
b7edebf8 | 307 | if(inp.empty()) |
308 | return false; | |
309 | lat=atof(inp.c_str()); | |
310 | auto pos=inp.find(' '); | |
311 | if(pos != string::npos) | |
312 | lon=atof(inp.c_str() + pos); | |
313 | return true; | |
314 | } | |
315 | ||
0540d223 | 316 | static bool getLatLon(const std::string& ip, string& loc) |
317 | { | |
318 | int latdeg, latmin, londeg, lonmin; | |
319 | double latsec, lonsec; | |
320 | char lathem='X', lonhem='X'; | |
1bc56192 | 321 | |
0540d223 | 322 | double lat, lon; |
323 | if(!getLatLon(ip, lat, lon)) | |
324 | return false; | |
325 | ||
326 | if(lat > 0) { | |
327 | lathem='N'; | |
328 | } | |
329 | else { | |
330 | lat = -lat; | |
331 | lathem='S'; | |
332 | } | |
333 | ||
334 | if(lon > 0) { | |
335 | lonhem='E'; | |
336 | } | |
337 | else { | |
338 | lon = -lon; | |
339 | lonhem='W'; | |
340 | } | |
341 | ||
342 | /* | |
343 | >>> deg = int(R) | |
344 | >>> min = int((R - int(R)) * 60.0) | |
345 | >>> sec = (((R - int(R)) * 60.0) - min) * 60.0 | |
346 | >>> print("{}º {}' {}\"".format(deg, min, sec)) | |
347 | */ | |
348 | ||
1bc56192 | 349 | |
0540d223 | 350 | latdeg = lat; |
351 | latmin = (lat - latdeg)*60.0; | |
352 | latsec = (((lat - latdeg)*60.0) - latmin)*60.0; | |
353 | ||
354 | londeg = lon; | |
355 | lonmin = (lon - londeg)*60.0; | |
356 | lonsec = (((lon - londeg)*60.0) - lonmin)*60.0; | |
357 | ||
358 | // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m | |
359 | ||
360 | boost::format fmt("%d %d %d %c %d %d %d %c 0.00m 1.00m 10000.00m 10.00m"); | |
361 | ||
362 | loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str(); | |
363 | return true; | |
364 | } | |
975f0839 | 365 | |
1bc56192 | 366 | static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips) |
975f0839 | 367 | { |
6d7e1fd3 CHB |
368 | if (wips.empty()) { |
369 | throw std::invalid_argument("The IP list cannot be empty"); | |
370 | } | |
975f0839 | 371 | map<double,vector<ComboAddress> > ranked; |
372 | double wlat=0, wlon=0; | |
373 | getLatLon(bestwho.toString(), wlat, wlon); | |
374 | // cout<<"bestwho "<<wlat<<", "<<wlon<<endl; | |
375 | vector<string> ret; | |
376 | for(const auto& c : wips) { | |
377 | double lat=0, lon=0; | |
378 | getLatLon(c.toString(), lat, lon); | |
379 | // cout<<c.toString()<<": "<<lat<<", "<<lon<<endl; | |
380 | double latdiff = wlat-lat; | |
381 | double londiff = wlon-lon; | |
382 | if(londiff > 180) | |
1bc56192 | 383 | londiff = 360 - londiff; |
975f0839 | 384 | double dist2=latdiff*latdiff + londiff*londiff; |
385 | // cout<<" distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree | |
386 | ranked[dist2].push_back(c); | |
387 | } | |
45d4e670 | 388 | return ranked.begin()->second[dns_random(ranked.begin()->second.size())]; |
975f0839 | 389 | } |
390 | ||
c9d40b1f | 391 | static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid) |
392 | { | |
393 | static UeberBackend ub; | |
394 | static std::mutex mut; | |
395 | std::lock_guard<std::mutex> lock(mut); | |
396 | ub.lookup(QType(qtype), name, nullptr, zoneid); | |
397 | DNSZoneRecord dr; | |
398 | vector<DNSZoneRecord> ret; | |
399 | while(ub.get(dr)) { | |
400 | ret.push_back(dr); | |
401 | } | |
402 | return ret; | |
403 | } | |
975f0839 | 404 | |
2fe7bbf8 | 405 | static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue) |
5ca4872a | 406 | { |
2fe7bbf8 | 407 | string selector=defaultValue; |
5ca4872a | 408 | if(options) { |
2fe7bbf8 OV |
409 | if(options->count(name)) |
410 | selector=options->find(name)->second; | |
5ca4872a | 411 | } |
2fe7bbf8 OV |
412 | return selector; |
413 | } | |
414 | ||
415 | static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates) | |
416 | { | |
417 | vector<ComboAddress> ret; | |
5ca4872a | 418 | |
707cedf3 | 419 | if(selector=="all") |
2fe7bbf8 OV |
420 | return candidates; |
421 | else if(selector=="random") | |
422 | ret.emplace_back(pickrandom(candidates)); | |
1bc56192 | 423 | else if(selector=="pickclosest") |
2fe7bbf8 | 424 | ret.emplace_back(pickclosest(bestwho, candidates)); |
5ca4872a | 425 | else if(selector=="hashed") |
2fe7bbf8 OV |
426 | ret.emplace_back(hashed(bestwho, candidates)); |
427 | else { | |
428 | g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl; | |
429 | ret.emplace_back(pickrandom(candidates)); | |
430 | } | |
431 | ||
432 | return ret; | |
433 | } | |
5ca4872a | 434 | |
2fe7bbf8 OV |
435 | static vector<string> convIpListToString(const vector<ComboAddress> &comboAddresses) |
436 | { | |
437 | vector<string> ret; | |
c9768b80 CHB |
438 | |
439 | for (const auto& c : comboAddresses) { | |
2fe7bbf8 | 440 | ret.emplace_back(c.toString()); |
c9768b80 CHB |
441 | } |
442 | ||
2fe7bbf8 | 443 | return ret; |
5ca4872a | 444 | } |
445 | ||
1bc56192 CHB |
446 | static vector<ComboAddress> convIplist(const iplist_t& src) |
447 | { | |
448 | vector<ComboAddress> ret; | |
449 | ||
c9768b80 | 450 | for(const auto& ip : src) { |
1bc56192 | 451 | ret.emplace_back(ip.second); |
c9768b80 | 452 | } |
1bc56192 CHB |
453 | |
454 | return ret; | |
455 | } | |
456 | ||
457 | static vector<pair<int, ComboAddress> > convWIplist(std::unordered_map<int, wiplist_t > src) | |
458 | { | |
459 | vector<pair<int,ComboAddress> > ret; | |
460 | ||
c9768b80 | 461 | for(const auto& i : src) { |
1bc56192 | 462 | ret.emplace_back(atoi(i.second.at(1).c_str()), ComboAddress(i.second.at(2))); |
c9768b80 | 463 | } |
1bc56192 CHB |
464 | |
465 | return ret; | |
466 | } | |
467 | ||
32829819 PD |
468 | static thread_local unique_ptr<AuthLua4> s_LUA; |
469 | bool g_LuaRecordSharedState; | |
fd1bdfb3 | 470 | |
1bc56192 | 471 | std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype) |
b7edebf8 | 472 | { |
32829819 PD |
473 | if(!s_LUA || // we don't have a Lua state yet |
474 | !g_LuaRecordSharedState) { // or we want a new one even if we had one | |
475 | s_LUA = make_unique<AuthLua4>(); | |
fd1bdfb3 | 476 | } |
1bc56192 | 477 | |
b7edebf8 | 478 | std::vector<shared_ptr<DNSRecordContent>> ret; |
1bc56192 | 479 | |
32829819 | 480 | LuaContext& lua = *s_LUA->getLua(); |
b7edebf8 | 481 | lua.writeVariable("qname", query); |
482 | lua.writeVariable("who", dnsp.getRemote()); | |
25e92bfa | 483 | lua.writeVariable("dh", (dnsheader*)&dnsp.d); |
484 | lua.writeVariable("dnssecOK", dnsp.d_dnssecOk); | |
485 | lua.writeVariable("tcp", dnsp.d_tcp); | |
486 | lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit); | |
b7edebf8 | 487 | ComboAddress bestwho; |
488 | if(dnsp.hasEDNSSubnet()) { | |
0540d223 | 489 | lua.writeVariable("ecswho", dnsp.getRealRemote()); |
b7edebf8 | 490 | bestwho=dnsp.getRealRemote().getNetwork(); |
b7edebf8 | 491 | } |
492 | else { | |
e2cefbf5 | 493 | lua.writeVariable("ecswho", nullptr); |
b7edebf8 | 494 | bestwho=dnsp.getRemote(); |
495 | } | |
496 | ||
5a6d5b08 PD |
497 | lua.writeVariable("bestwho", bestwho); |
498 | ||
b7edebf8 | 499 | lua.writeFunction("latlon", [&bestwho]() { |
0540d223 | 500 | double lat, lon; |
501 | getLatLon(bestwho.toString(), lat, lon); | |
502 | return std::to_string(lat)+" "+std::to_string(lon); | |
503 | }); | |
504 | ||
505 | lua.writeFunction("latlonloc", [&bestwho]() { | |
506 | string loc; | |
507 | getLatLon(bestwho.toString(), loc); | |
0540d223 | 508 | return loc; |
b7edebf8 | 509 | }); |
975f0839 | 510 | |
1bc56192 | 511 | |
6d7e1fd3 | 512 | lua.writeFunction("closestMagic", [&bestwho,&query]() { |
b7edebf8 | 513 | vector<ComboAddress> candidates; |
cc5c4f6b | 514 | // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org |
b7edebf8 | 515 | for(auto l : query.getRawLabels()) { |
516 | boost::replace_all(l, "-", "."); | |
b7edebf8 | 517 | try { |
518 | candidates.emplace_back(l); | |
6d7e1fd3 | 519 | } catch (const PDNSException& e) { |
cc5c4f6b CHB |
520 | // no need to continue as we most likely reached the end of the ip list |
521 | break ; | |
b7edebf8 | 522 | } |
523 | } | |
1bc56192 | 524 | return pickclosest(bestwho, candidates).toString(); |
b7edebf8 | 525 | }); |
1bc56192 | 526 | |
7fedb52a | 527 | lua.writeFunction("latlonMagic", [&query](){ |
528 | auto labels= query.getRawLabels(); | |
529 | if(labels.size()<4) | |
530 | return std::string("unknown"); | |
531 | double lat, lon; | |
532 | getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon); | |
533 | return std::to_string(lat)+" "+std::to_string(lon); | |
534 | }); | |
c9d40b1f | 535 | |
1bc56192 | 536 | |
17e300d7 | 537 | lua.writeFunction("createReverse", [&query](string suffix, boost::optional<std::unordered_map<string,string>> e){ |
c9d40b1f | 538 | try { |
539 | auto labels= query.getRawLabels(); | |
540 | if(labels.size()<4) | |
541 | return std::string("unknown"); | |
542 | ||
543 | vector<ComboAddress> candidates; | |
544 | ||
545 | // exceptions are relative to zone | |
546 | // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa | |
547 | // e["1.2.3.4"]="bert.powerdns.com" - should match, easy enough to do | |
1bc56192 | 548 | // the issue is with classless delegation.. |
c9d40b1f | 549 | if(e) { |
550 | ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0); | |
551 | const auto& uom = *e; | |
552 | for(const auto& c : uom) | |
553 | if(ComboAddress(c.first, 0) == req) | |
554 | return c.second; | |
555 | } | |
1bc56192 | 556 | |
c9d40b1f | 557 | |
558 | boost::format fmt(suffix); | |
559 | fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) ); | |
560 | fmt % labels[3] % labels[2] % labels[1] % labels[0]; | |
561 | ||
562 | fmt % (labels[3]+"-"+labels[2]+"-"+labels[1]+"-"+labels[0]); | |
563 | ||
564 | boost::format fmt2("%02x%02x%02x%02x"); | |
565 | for(int i=3; i>=0; --i) | |
566 | fmt2 % atoi(labels[i].c_str()); | |
567 | ||
568 | fmt % (fmt2.str()); | |
569 | ||
570 | return fmt.str(); | |
571 | } | |
572 | catch(std::exception& e) { | |
f5f3d7f5 | 573 | g_log<<Logger::Error<<"error: "<<e.what()<<endl; |
c9d40b1f | 574 | } |
575 | return std::string("error"); | |
576 | }); | |
577 | ||
578 | lua.writeFunction("createForward", [&zone, &query]() { | |
579 | DNSName rel=query.makeRelative(zone); | |
580 | auto parts = rel.getRawLabels(); | |
581 | if(parts.size()==4) | |
582 | return parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]; | |
583 | if(parts.size()==1) { | |
584 | // either hex string, or 12-13-14-15 | |
cb6bd1a9 | 585 | // cout<<parts[0]<<endl; |
df6ad4ee | 586 | unsigned int x1, x2, x3, x4; |
c9d40b1f | 587 | if(sscanf(parts[0].c_str()+2, "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) { |
588 | return std::to_string(x1)+"."+std::to_string(x2)+"."+std::to_string(x3)+"."+std::to_string(x4); | |
589 | } | |
1bc56192 CHB |
590 | |
591 | ||
c9d40b1f | 592 | } |
593 | return std::string("0.0.0.0"); | |
594 | }); | |
595 | ||
596 | lua.writeFunction("createForward6", [&query,&zone]() { | |
597 | DNSName rel=query.makeRelative(zone); | |
598 | auto parts = rel.getRawLabels(); | |
599 | if(parts.size()==8) { | |
600 | string tot; | |
601 | for(int i=0; i<8; ++i) { | |
602 | if(i) | |
603 | tot.append(1,':'); | |
604 | tot+=parts[i]; | |
605 | } | |
606 | ComboAddress ca(tot); | |
607 | return ca.toString(); | |
608 | } | |
609 | else if(parts.size()==1) { | |
610 | boost::replace_all(parts[0],"-",":"); | |
611 | ComboAddress ca(parts[0]); | |
612 | return ca.toString(); | |
613 | } | |
614 | ||
615 | return std::string("::"); | |
616 | }); | |
617 | ||
1bc56192 | 618 | |
17e300d7 | 619 | lua.writeFunction("createReverse6", [&query](string suffix, boost::optional<std::unordered_map<string,string>> e){ |
c9d40b1f | 620 | vector<ComboAddress> candidates; |
621 | ||
622 | try { | |
623 | auto labels= query.getRawLabels(); | |
624 | if(labels.size()<32) | |
625 | return std::string("unknown"); | |
c9d40b1f | 626 | boost::format fmt(suffix); |
627 | fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) ); | |
1bc56192 | 628 | |
c9d40b1f | 629 | |
630 | string together; | |
631 | vector<string> quads; | |
632 | for(int i=0; i<8; ++i) { | |
633 | if(i) | |
634 | together+=":"; | |
635 | string quad; | |
636 | for(int j=0; j <4; ++j) { | |
637 | quad.append(1, labels[31-i*4-j][0]); | |
638 | together += labels[31-i*4-j][0]; | |
639 | } | |
640 | quads.push_back(quad); | |
641 | } | |
642 | ComboAddress ip6(together,0); | |
643 | ||
644 | if(e) { | |
645 | auto& addrs=*e; | |
646 | for(const auto& addr: addrs) { | |
647 | // this makes sure we catch all forms of the address | |
648 | if(ComboAddress(addr.first,0)==ip6) | |
649 | return addr.second; | |
650 | } | |
651 | } | |
1bc56192 | 652 | |
c9d40b1f | 653 | string dashed=ip6.toString(); |
654 | boost::replace_all(dashed, ":", "-"); | |
1bc56192 | 655 | |
c9d40b1f | 656 | for(int i=31; i>=0; --i) |
657 | fmt % labels[i]; | |
658 | fmt % dashed; | |
659 | ||
660 | for(const auto& quad : quads) | |
661 | fmt % quad; | |
1bc56192 | 662 | |
c9d40b1f | 663 | return fmt.str(); |
664 | } | |
665 | catch(std::exception& e) { | |
f5f3d7f5 | 666 | g_log<<Logger::Error<<"LUA Record xception: "<<e.what()<<endl; |
c9d40b1f | 667 | } |
668 | catch(PDNSException& e) { | |
f5f3d7f5 | 669 | g_log<<Logger::Error<<"LUA Record exception: "<<e.reason<<endl; |
c9d40b1f | 670 | } |
671 | return std::string("unknown"); | |
672 | }); | |
673 | ||
1bc56192 CHB |
674 | |
675 | /* | |
676 | * Simplistic test to see if an IP address listens on a certain port | |
677 | * Will return a single IP address from the set of available IP addresses. If | |
678 | * no IP address is available, will return a random element of the set of | |
679 | * addresses suppplied for testing. | |
680 | * | |
681 | * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })" | |
682 | */ | |
4a913a28 | 683 | lua.writeFunction("ifportup", [&bestwho](int port, const vector<pair<int, string> >& ips, const boost::optional<std::unordered_map<string,string>> options) { |
1bc56192 CHB |
684 | vector<ComboAddress> candidates, unavailables; |
685 | opts_t opts; | |
686 | vector<ComboAddress > conv; | |
5daed657 | 687 | std::string selector; |
1bc56192 | 688 | |
25bcfaec | 689 | if(options) |
690 | opts = *options; | |
b7edebf8 | 691 | for(const auto& i : ips) { |
692 | ComboAddress rem(i.second, port); | |
1bc56192 | 693 | if(g_up.isUp(rem, opts)) { |
b7edebf8 | 694 | candidates.push_back(rem); |
1bc56192 CHB |
695 | } |
696 | else { | |
697 | unavailables.push_back(rem); | |
698 | } | |
b7edebf8 | 699 | } |
5daed657 CHB |
700 | if(!candidates.empty()) { |
701 | // use regular selector | |
702 | selector = getOptionValue(options, "selector", "random"); | |
703 | } else { | |
704 | // All units are down, apply backupSelector on all candidates | |
1bc56192 | 705 | candidates = std::move(unavailables); |
5daed657 | 706 | selector = getOptionValue(options, "backupSelector", "random"); |
b7edebf8 | 707 | } |
b7edebf8 | 708 | |
5daed657 | 709 | vector<ComboAddress> res = useSelector(selector, bestwho, candidates); |
2fe7bbf8 | 710 | return convIpListToString(res); |
1bc56192 | 711 | }); |
b7edebf8 | 712 | |
5ca4872a | 713 | lua.writeFunction("ifurlup", [&bestwho](const std::string& url, |
1bc56192 CHB |
714 | const boost::variant<iplist_t, ipunitlist_t>& ips, |
715 | boost::optional<opts_t> options) { | |
b7edebf8 | 716 | |
717 | vector<vector<ComboAddress> > candidates; | |
1bc56192 | 718 | opts_t opts; |
b7edebf8 | 719 | if(options) |
720 | opts = *options; | |
1bc56192 CHB |
721 | if(auto simple = boost::get<iplist_t>(&ips)) { |
722 | vector<ComboAddress> unit = convIplist(*simple); | |
b7edebf8 | 723 | candidates.push_back(unit); |
724 | } else { | |
1bc56192 | 725 | auto units = boost::get<ipunitlist_t>(ips); |
b7edebf8 | 726 | for(const auto& u : units) { |
1bc56192 | 727 | vector<ComboAddress> unit = convIplist(u.second); |
b7edebf8 | 728 | candidates.push_back(unit); |
729 | } | |
730 | } | |
731 | ||
b7edebf8 | 732 | for(const auto& unit : candidates) { |
733 | vector<ComboAddress> available; | |
1bc56192 CHB |
734 | for(const auto& c : unit) { |
735 | if(g_up.isUp(c, url, opts)) { | |
b7edebf8 | 736 | available.push_back(c); |
1bc56192 | 737 | } |
b7edebf8 | 738 | } |
1bc56192 | 739 | if(!available.empty()) { |
2fe7bbf8 OV |
740 | vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), bestwho, available); |
741 | return convIpListToString(res); | |
1bc56192 CHB |
742 | } |
743 | } | |
744 | ||
5daed657 | 745 | // All units down, apply backupSelector on all candidates |
1bc56192 | 746 | vector<ComboAddress> ret{}; |
b7edebf8 | 747 | for(const auto& unit : candidates) { |
1bc56192 | 748 | ret.insert(ret.end(), unit.begin(), unit.end()); |
b7edebf8 | 749 | } |
750 | ||
5daed657 | 751 | vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), bestwho, ret); |
2fe7bbf8 OV |
752 | return convIpListToString(res); |
753 | }); | |
b7edebf8 | 754 | |
975f0839 | 755 | |
975f0839 | 756 | /* idea: we have policies on vectors of ComboAddresses, like |
1bc56192 | 757 | random, pickwrandom, pickwhashed, pickclosest. In C++ this is ComboAddress in, |
975f0839 | 758 | ComboAddress out. In Lua, vector string in, string out */ |
1bc56192 CHB |
759 | |
760 | /* | |
761 | * Returns a random IP address from the supplied list | |
762 | * @example pickrandom({ '1.2.3.4', '5.4.3.2' })" | |
763 | */ | |
764 | lua.writeFunction("pickrandom", [](const iplist_t& ips) { | |
6d7e1fd3 | 765 | vector<ComboAddress> conv = convIplist(ips); |
1bc56192 CHB |
766 | |
767 | return pickrandom(conv).toString(); | |
b7edebf8 | 768 | }); |
769 | ||
b7edebf8 | 770 | |
1bc56192 CHB |
771 | /* |
772 | * Returns a random IP address from the supplied list, as weighted by the | |
773 | * various ``weight`` parameters | |
774 | * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} }) | |
775 | */ | |
776 | lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) { | |
777 | vector<pair<int,ComboAddress> > conv = convWIplist(ips); | |
778 | ||
779 | return pickwrandom(conv).toString(); | |
975f0839 | 780 | }); |
781 | ||
1bc56192 CHB |
782 | /* |
783 | * Based on the hash of `bestwho`, returns an IP address from the list | |
784 | * supplied, as weighted by the various `weight` parameters | |
785 | * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} }) | |
786 | */ | |
787 | lua.writeFunction("pickwhashed", [&bestwho](std::unordered_map<int, wiplist_t > ips) { | |
975f0839 | 788 | vector<pair<int,ComboAddress> > conv; |
1bc56192 CHB |
789 | |
790 | for(auto& i : ips) | |
975f0839 | 791 | conv.emplace_back(atoi(i.second[1].c_str()), ComboAddress(i.second[2])); |
1bc56192 CHB |
792 | |
793 | return pickwhashed(bestwho, conv).toString(); | |
975f0839 | 794 | }); |
795 | ||
796 | ||
1bc56192 CHB |
797 | lua.writeFunction("pickclosest", [&bestwho](const iplist_t& ips) { |
798 | vector<ComboAddress > conv = convIplist(ips); | |
799 | ||
800 | return pickclosest(bestwho, conv).toString(); | |
801 | ||
b7edebf8 | 802 | }); |
803 | ||
1bc56192 | 804 | |
17e300d7 | 805 | lua.writeFunction("report", [](string event, boost::optional<string> line){ |
b7edebf8 | 806 | throw std::runtime_error("Script took too long"); |
807 | }); | |
af68014f CHB |
808 | if (g_luaRecordExecLimit > 0) { |
809 | lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit)); | |
810 | } | |
b7edebf8 | 811 | |
1d56dac6 | 812 | // TODO: make this better. Accept netmask/CA objects; provide names for the attr constants |
f9423419 | 813 | lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) { |
1d56dac6 PD |
814 | return getGeo(ip, attr); |
815 | }); | |
816 | ||
b7edebf8 | 817 | typedef const boost::variant<string,vector<pair<int,string> > > combovar_t; |
818 | lua.writeFunction("continent", [&bestwho](const combovar_t& continent) { | |
f9423419 | 819 | string res=getGeo(bestwho.toString(), GeoIPInterface::Continent); |
b7edebf8 | 820 | return doCompare(continent, res, [](const std::string& a, const std::string& b) { |
821 | return !strcasecmp(a.c_str(), b.c_str()); | |
822 | }); | |
823 | }); | |
824 | ||
825 | lua.writeFunction("asnum", [&bestwho](const combovar_t& asns) { | |
f9423419 | 826 | string res=getGeo(bestwho.toString(), GeoIPInterface::ASn); |
b7edebf8 | 827 | return doCompare(asns, res, [](const std::string& a, const std::string& b) { |
b7edebf8 | 828 | return !strcasecmp(a.c_str(), b.c_str()); |
829 | }); | |
830 | }); | |
1bc56192 | 831 | |
b7edebf8 | 832 | lua.writeFunction("country", [&bestwho](const combovar_t& var) { |
f9423419 | 833 | string res = getGeo(bestwho.toString(), GeoIPInterface::Country2); |
b7edebf8 | 834 | return doCompare(var, res, [](const std::string& a, const std::string& b) { |
835 | return !strcasecmp(a.c_str(), b.c_str()); | |
836 | }); | |
1bc56192 | 837 | |
b7edebf8 | 838 | }); |
839 | ||
1bc56192 | 840 | lua.writeFunction("netmask", [bestwho](const iplist_t& ips) { |
b7edebf8 | 841 | for(const auto& i :ips) { |
842 | Netmask nm(i.second); | |
843 | if(nm.match(bestwho)) | |
844 | return true; | |
845 | } | |
846 | return false; | |
847 | }); | |
848 | ||
849 | /* { | |
850 | { | |
1bc56192 | 851 | {'192.168.0.0/16', '10.0.0.0/8'}, |
b7edebf8 | 852 | {'192.168.20.20', '192.168.20.21'} |
853 | }, | |
854 | { | |
855 | {'0.0.0.0/0'}, {'192.0.2.1'} | |
856 | } | |
857 | } | |
1bc56192 CHB |
858 | */ |
859 | lua.writeFunction("view", [bestwho](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) { | |
b7edebf8 | 860 | for(const auto& rule : in) { |
861 | const auto& netmasks=rule.second[0].second; | |
862 | const auto& destinations=rule.second[1].second; | |
863 | for(const auto& nmpair : netmasks) { | |
864 | Netmask nm(nmpair.second); | |
865 | if(nm.match(bestwho)) { | |
b51ef4f9 | 866 | return destinations[dns_random(destinations.size())].second; |
b7edebf8 | 867 | } |
868 | } | |
869 | } | |
870 | return std::string(); | |
871 | } | |
872 | ); | |
1bc56192 CHB |
873 | |
874 | ||
b7edebf8 | 875 | lua.writeFunction("include", [&lua,zone,zoneid](string record) { |
876 | try { | |
c9d40b1f | 877 | vector<DNSZoneRecord> drs = lookup(DNSName(record) +zone, QType::LUA, zoneid); |
878 | for(const auto& dr : drs) { | |
b7edebf8 | 879 | auto lr = getRR<LUARecordContent>(dr.dr); |
880 | lua.executeCode(lr->getCode()); | |
881 | } | |
25bcfaec | 882 | } |
883 | catch(std::exception& e) { | |
f5f3d7f5 | 884 | g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<(DNSName(record)+zone)<<": "<<e.what()<<endl; |
25bcfaec | 885 | } |
b7edebf8 | 886 | }); |
887 | ||
b7edebf8 | 888 | try { |
889 | string actual; | |
890 | if(!code.empty() && code[0]!=';') | |
6180eab4 PD |
891 | actual = "return " + code; |
892 | else | |
893 | actual = code.substr(1); | |
c9d40b1f | 894 | |
b7edebf8 | 895 | auto content=lua.executeCode<boost::variant<string, vector<pair<int, string> > > >(actual); |
c9d40b1f | 896 | |
b7edebf8 | 897 | vector<string> contents; |
898 | if(auto str = boost::get<string>(&content)) | |
899 | contents.push_back(*str); | |
900 | else | |
901 | for(const auto& c : boost::get<vector<pair<int,string>>>(content)) | |
902 | contents.push_back(c.second); | |
1bc56192 | 903 | |
b7edebf8 | 904 | for(const auto& content: contents) { |
905 | if(qtype==QType::TXT) | |
98fa3598 | 906 | ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, '"'+content+'"' )); |
b7edebf8 | 907 | else |
98fa3598 | 908 | ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, content )); |
b7edebf8 | 909 | } |
1bc56192 | 910 | } catch(std::exception &e) { |
3217f4ea PD |
911 | g_log<<Logger::Error<<"Lua record reported: "<<e.what(); |
912 | try { | |
913 | std::rethrow_if_nested(e); | |
914 | g_log<<endl; | |
915 | } catch(const std::exception& ne) { | |
916 | g_log << ": " << ne.what() << std::endl; | |
917 | } | |
918 | catch(const PDNSException& ne) { | |
919 | g_log << ": " << ne.reason << std::endl; | |
920 | } | |
1bc56192 | 921 | throw ; |
b7edebf8 | 922 | } |
923 | ||
924 | return ret; | |
925 | } |