]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/rec-lua-conf.cc
dnsdist: Add more TCP metrics
[thirdparty/pdns.git] / pdns / rec-lua-conf.cc
1 #include "config.h"
2 #include "ext/luawrapper/include/LuaContext.hpp"
3
4 #include <fstream>
5 #include <thread>
6 #include "namespaces.hh"
7 #include "logger.hh"
8 #include "rec-lua-conf.hh"
9 #include "sortlist.hh"
10 #include "filterpo.hh"
11 #include "syncres.hh"
12 #include "rpzloader.hh"
13 #include "base64.hh"
14 #include "remote_logger.hh"
15 #include "validate.hh"
16 #include "validate-recursor.hh"
17 #include "root-dnssec.hh"
18
19 GlobalStateHolder<LuaConfigItems> g_luaconfs;
20
21 /* SO HOW DOES THIS WORK! AND PLEASE PAY ATTENTION!
22 This function can be called at any time. It is expected to overwrite all the contents
23 of LuaConfigItems, which is held in a GlobalStateHolder for RCU properties.
24
25 This function can be called again at a later date, so you must make sure that anything you
26 allow to be configured from here lives in g_luaconfs AND NOWHERE ELSE.
27
28 If someone loads an empty Lua file, the default LuaConfigItems struct MUST MAKE SENSE.
29
30 To make this easy on you, here is a LuaConfigItems constructor where you
31 can set sane defaults:
32 */
33
34 LuaConfigItems::LuaConfigItems()
35 {
36 DNSName root("."); // don't use g_rootdnsname here, it might not exist yet
37 for (const auto &dsRecord : rootDSs) {
38 auto ds=std::dynamic_pointer_cast<DSRecordContent>(DSRecordContent::make(dsRecord));
39 dsAnchors[root].insert(*ds);
40 }
41 }
42
43 /* DID YOU READ THE STORY ABOVE? */
44
45 template <typename C>
46 typename C::value_type::second_type constGet(const C& c, const std::string& name)
47 {
48 auto iter = c.find(name);
49 if(iter == c.end())
50 return 0;
51 return iter->second;
52 }
53
54 typedef std::unordered_map<std::string, boost::variant<bool, uint32_t, std::string > > rpzOptions_t;
55
56 static void parseRPZParameters(rpzOptions_t& have, std::string& polName, boost::optional<DNSFilterEngine::Policy>& defpol, bool& defpolOverrideLocal, uint32_t& maxTTL, size_t& zoneSizeHint)
57 {
58 if(have.count("policyName")) {
59 polName = boost::get<std::string>(have["policyName"]);
60 }
61 if(have.count("defpol")) {
62 defpol=DNSFilterEngine::Policy();
63 defpol->d_kind = (DNSFilterEngine::PolicyKind)boost::get<uint32_t>(have["defpol"]);
64 defpol->d_name = std::make_shared<std::string>(polName);
65 if(defpol->d_kind == DNSFilterEngine::PolicyKind::Custom) {
66 defpol->d_custom.push_back(DNSRecordContent::mastermake(QType::CNAME, QClass::IN,
67 boost::get<string>(have["defcontent"])));
68
69 if(have.count("defttl"))
70 defpol->d_ttl = static_cast<int32_t>(boost::get<uint32_t>(have["defttl"]));
71 else
72 defpol->d_ttl = -1; // get it from the zone
73 }
74
75 if (have.count("defpolOverrideLocalData")) {
76 defpolOverrideLocal = boost::get<bool>(have["defpolOverrideLocalData"]);
77 }
78 }
79 if(have.count("maxTTL")) {
80 maxTTL = boost::get<uint32_t>(have["maxTTL"]);
81 }
82 if(have.count("zoneSizeHint")) {
83 zoneSizeHint = static_cast<size_t>(boost::get<uint32_t>(have["zoneSizeHint"]));
84 }
85 }
86
87 #if HAVE_PROTOBUF
88 typedef std::unordered_map<std::string, boost::variant<bool, uint64_t, std::string, std::vector<std::pair<int,std::string> > > > protobufOptions_t;
89
90 static void parseProtobufOptions(boost::optional<protobufOptions_t> vars, ProtobufExportConfig& config)
91 {
92 if (!vars) {
93 return;
94 }
95
96 if (vars->count("timeout")) {
97 config.timeout = boost::get<uint64_t>((*vars)["timeout"]);
98 }
99
100 if (vars->count("maxQueuedEntries")) {
101 config.maxQueuedEntries = boost::get<uint64_t>((*vars)["maxQueuedEntries"]);
102 }
103
104 if (vars->count("reconnectWaitTime")) {
105 config.reconnectWaitTime = boost::get<uint64_t>((*vars)["reconnectWaitTime"]);
106 }
107
108 if (vars->count("asyncConnect")) {
109 config.asyncConnect = boost::get<bool>((*vars)["asyncConnect"]);
110 }
111
112 if (vars->count("taggedOnly")) {
113 config.taggedOnly = boost::get<bool>((*vars)["taggedOnly"]);
114 }
115
116 if (vars->count("logQueries")) {
117 config.logQueries = boost::get<bool>((*vars)["logQueries"]);
118 }
119
120 if (vars->count("logResponses")) {
121 config.logResponses = boost::get<bool>((*vars)["logResponses"]);
122 }
123
124 if (vars->count("exportTypes")) {
125 config.exportTypes.clear();
126
127 auto types = boost::get<std::vector<std::pair<int, std::string>>>((*vars)["exportTypes"]);
128 for (const auto& pair : types) {
129 const auto type = pair.second;
130 bool found = false;
131
132 for (const auto& entry : QType::names) {
133 if (entry.first == type) {
134 found = true;
135 config.exportTypes.insert(entry.second);
136 break;
137 }
138 }
139
140 if (!found) {
141 throw std::runtime_error("Unknown QType '" + type + "' in protobuf's export types");
142 }
143 }
144 }
145 }
146 #endif /* HAVE_PROTOBUF */
147
148 void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads)
149 {
150 LuaConfigItems lci;
151
152 LuaContext Lua;
153 if(fname.empty())
154 return;
155 ifstream ifs(fname);
156 if(!ifs)
157 throw PDNSException("Cannot open file '"+fname+"': "+strerror(errno));
158
159 auto luaconfsLocal = g_luaconfs.getLocal();
160 lci.generation = luaconfsLocal->generation + 1;
161
162 // pdnslog here is compatible with pdnslog in lua-base4.cc.
163 Lua.writeFunction("pdnslog", [](const std::string& msg, boost::optional<int> loglevel) { g_log << (Logger::Urgency)loglevel.get_value_or(Logger::Warning) << msg<<endl; });
164 std::unordered_map<string, std::unordered_map<string, int>> pdns_table;
165 pdns_table["loglevels"] = std::unordered_map<string, int>{
166 {"Alert", LOG_ALERT},
167 {"Critical", LOG_CRIT},
168 {"Debug", LOG_DEBUG},
169 {"Emergency", LOG_EMERG},
170 {"Info", LOG_INFO},
171 {"Notice", LOG_NOTICE},
172 {"Warning", LOG_WARNING},
173 {"Error", LOG_ERR}
174 };
175 Lua.writeVariable("pdns", pdns_table);
176
177 Lua.writeFunction("clearSortlist", [&lci]() { lci.sortlist.clear(); });
178
179 /* we can get: "1.2.3.4"
180 {"1.2.3.4", "4.5.6.7"}
181 {"1.2.3.4", {"4.5.6.7", "8.9.10.11"}}
182 */
183
184 map<string,DNSFilterEngine::PolicyKind> pmap{
185 {"NoAction", DNSFilterEngine::PolicyKind::NoAction},
186 {"Drop", DNSFilterEngine::PolicyKind::Drop},
187 {"NXDOMAIN", DNSFilterEngine::PolicyKind::NXDOMAIN},
188 {"NODATA", DNSFilterEngine::PolicyKind::NODATA},
189 {"Truncate", DNSFilterEngine::PolicyKind::Truncate},
190 {"Custom", DNSFilterEngine::PolicyKind::Custom}
191 };
192 Lua.writeVariable("Policy", pmap);
193
194 Lua.writeFunction("rpzFile", [&lci](const string& filename, boost::optional<rpzOptions_t> options) {
195 try {
196 boost::optional<DNSFilterEngine::Policy> defpol;
197 bool defpolOverrideLocal = true;
198 std::string polName("rpzFile");
199 std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
200 uint32_t maxTTL = std::numeric_limits<uint32_t>::max();
201 if(options) {
202 auto& have = *options;
203 size_t zoneSizeHint = 0;
204 parseRPZParameters(have, polName, defpol, defpolOverrideLocal, maxTTL, zoneSizeHint);
205 if (zoneSizeHint > 0) {
206 zone->reserve(zoneSizeHint);
207 }
208 }
209 g_log<<Logger::Warning<<"Loading RPZ from file '"<<filename<<"'"<<endl;
210 zone->setName(polName);
211 loadRPZFromFile(filename, zone, defpol, defpolOverrideLocal, maxTTL);
212 lci.dfe.addZone(zone);
213 g_log<<Logger::Warning<<"Done loading RPZ from file '"<<filename<<"'"<<endl;
214 }
215 catch(const std::exception& e) {
216 g_log<<Logger::Error<<"Unable to load RPZ zone from '"<<filename<<"': "<<e.what()<<endl;
217 }
218 });
219
220 Lua.writeFunction("rpzMaster", [&lci, &delayedThreads](const boost::variant<string, std::vector<std::pair<int, string> > >& masters_, const string& zoneName, boost::optional<rpzOptions_t> options) {
221
222 boost::optional<DNSFilterEngine::Policy> defpol;
223 bool defpolOverrideLocal = true;
224 std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
225 TSIGTriplet tt;
226 uint32_t refresh=0;
227 size_t maxReceivedXFRMBytes = 0;
228 uint16_t axfrTimeout = 20;
229 uint32_t maxTTL = std::numeric_limits<uint32_t>::max();
230 ComboAddress localAddress;
231 std::vector<ComboAddress> masters;
232 if (masters_.type() == typeid(string)) {
233 masters.push_back(ComboAddress(boost::get<std::string>(masters_), 53));
234 }
235 else {
236 for (const auto& master : boost::get<std::vector<std::pair<int, std::string>>>(masters_)) {
237 masters.push_back(ComboAddress(master.second, 53));
238 }
239 }
240
241 size_t zoneIdx;
242 std::string dumpFile;
243 std::shared_ptr<SOARecordContent> sr = nullptr;
244
245 try {
246 std::string seedFile;
247 std::string polName(zoneName);
248
249 if (options) {
250 auto& have = *options;
251 size_t zoneSizeHint = 0;
252 parseRPZParameters(have, polName, defpol, defpolOverrideLocal, maxTTL, zoneSizeHint);
253 if (zoneSizeHint > 0) {
254 zone->reserve(zoneSizeHint);
255 }
256
257 if(have.count("tsigname")) {
258 tt.name=DNSName(toLower(boost::get<string>(have["tsigname"])));
259 tt.algo=DNSName(toLower(boost::get<string>(have[ "tsigalgo"])));
260 if(B64Decode(boost::get<string>(have[ "tsigsecret"]), tt.secret))
261 throw std::runtime_error("TSIG secret is not valid Base-64 encoded");
262 }
263
264 if(have.count("refresh")) {
265 refresh = boost::get<uint32_t>(have["refresh"]);
266 }
267
268 if(have.count("maxReceivedMBytes")) {
269 maxReceivedXFRMBytes = static_cast<size_t>(boost::get<uint32_t>(have["maxReceivedMBytes"]));
270 }
271
272 if(have.count("localAddress")) {
273 localAddress = ComboAddress(boost::get<string>(have["localAddress"]));
274 }
275
276 if(have.count("axfrTimeout")) {
277 axfrTimeout = static_cast<uint16_t>(boost::get<uint32_t>(have["axfrTimeout"]));
278 }
279
280 if(have.count("seedFile")) {
281 seedFile = boost::get<std::string>(have["seedFile"]);
282 }
283
284 if(have.count("dumpFile")) {
285 dumpFile = boost::get<std::string>(have["dumpFile"]);
286 }
287 }
288
289 if (localAddress != ComboAddress()) {
290 // We were passed a localAddress, check if its AF matches the masters'
291 for (const auto& master : masters) {
292 if (localAddress.sin4.sin_family != master.sin4.sin_family) {
293 throw PDNSException("Master address("+master.toString()+") is not of the same Address Family as the local address ("+localAddress.toString()+").");
294 }
295 }
296 }
297
298 DNSName domain(zoneName);
299 zone->setDomain(domain);
300 zone->setName(polName);
301 zone->setRefresh(refresh);
302 zoneIdx = lci.dfe.addZone(zone);
303
304 if (!seedFile.empty()) {
305 g_log<<Logger::Info<<"Pre-loading RPZ zone "<<zoneName<<" from seed file '"<<seedFile<<"'"<<endl;
306 try {
307 sr = loadRPZFromFile(seedFile, zone, defpol, defpolOverrideLocal, maxTTL);
308
309 if (zone->getDomain() != domain) {
310 throw PDNSException("The RPZ zone " + zoneName + " loaded from the seed file (" + zone->getDomain().toString() + ") does not match the one passed in parameter (" + domain.toString() + ")");
311 }
312
313 if (sr == nullptr) {
314 throw PDNSException("The RPZ zone " + zoneName + " loaded from the seed file (" + zone->getDomain().toString() + ") has no SOA record");
315 }
316 }
317 catch(const std::exception& e) {
318 g_log<<Logger::Warning<<"Unable to pre-load RPZ zone "<<zoneName<<" from seed file '"<<seedFile<<"': "<<e.what()<<endl;
319 }
320 }
321 }
322 catch(const std::exception& e) {
323 g_log<<Logger::Error<<"Problem configuring 'rpzMaster': "<<e.what()<<endl;
324 exit(1); // FIXME proper exit code?
325 }
326 catch(const PDNSException& e) {
327 g_log<<Logger::Error<<"Problem configuring 'rpzMaster': "<<e.reason<<endl;
328 exit(1); // FIXME proper exit code?
329 }
330
331 delayedThreads.rpzMasterThreads.push_back(std::make_tuple(masters, defpol, defpolOverrideLocal, maxTTL, zoneIdx, tt, maxReceivedXFRMBytes, localAddress, axfrTimeout, sr, dumpFile));
332 });
333
334 typedef vector<pair<int,boost::variant<string, vector<pair<int, string> > > > > argvec_t;
335 Lua.writeFunction("addSortList",
336 [&lci](const std::string& formask_,
337 const boost::variant<string, argvec_t>& masks,
338 boost::optional<int> order_)
339 {
340 try {
341 Netmask formask(formask_);
342 int order = order_ ? (*order_) : lci.sortlist.getMaxOrder(formask)+1;
343 if(auto str = boost::get<string>(&masks))
344 lci.sortlist.addEntry(formask, Netmask(*str), order);
345 else {
346
347 auto vec = boost::get<argvec_t>(&masks);
348 for(const auto& e : *vec) {
349 if(auto s = boost::get<string>(&e.second)) {
350 lci.sortlist.addEntry(formask, Netmask(*s), order);
351 }
352 else {
353 const auto& v =boost::get<vector<pair<int, string> > >(e.second);
354 for(const auto& entry : v)
355 lci.sortlist.addEntry(formask, Netmask(entry.second), order);
356 }
357 ++order;
358 }
359 }
360 }
361 catch(std::exception& e) {
362 g_log<<Logger::Error<<"Error in addSortList: "<<e.what()<<endl;
363 }
364 });
365
366 Lua.writeFunction("addTA", [&lci](const std::string& who, const std::string& what) {
367 warnIfDNSSECDisabled("Warning: adding Trust Anchor for DNSSEC (addTA), but dnssec is set to 'off'!");
368 DNSName zone(who);
369 auto ds = std::dynamic_pointer_cast<DSRecordContent>(DSRecordContent::make(what));
370 lci.dsAnchors[zone].insert(*ds);
371 });
372
373 Lua.writeFunction("clearTA", [&lci](boost::optional<string> who) {
374 warnIfDNSSECDisabled("Warning: removing Trust Anchor for DNSSEC (clearTA), but dnssec is set to 'off'!");
375 if(who)
376 lci.dsAnchors.erase(DNSName(*who));
377 else
378 lci.dsAnchors.clear();
379 });
380
381 /* Remove in 4.3 */
382 Lua.writeFunction("addDS", [&lci](const std::string& who, const std::string& what) {
383 warnIfDNSSECDisabled("Warning: adding Trust Anchor for DNSSEC (addDS), but dnssec is set to 'off'!");
384 g_log<<Logger::Warning<<"addDS is deprecated and will be removed in the future, switch to addTA"<<endl;
385 DNSName zone(who);
386 auto ds = std::dynamic_pointer_cast<DSRecordContent>(DSRecordContent::make(what));
387 lci.dsAnchors[zone].insert(*ds);
388 });
389
390 /* Remove in 4.3 */
391 Lua.writeFunction("clearDS", [&lci](boost::optional<string> who) {
392 g_log<<Logger::Warning<<"clearDS is deprecated and will be removed in the future, switch to clearTA"<<endl;
393 warnIfDNSSECDisabled("Warning: removing Trust Anchor for DNSSEC (clearDS), but dnssec is set to 'off'!");
394 if(who)
395 lci.dsAnchors.erase(DNSName(*who));
396 else
397 lci.dsAnchors.clear();
398 });
399
400 Lua.writeFunction("addNTA", [&lci](const std::string& who, const boost::optional<std::string> why) {
401 warnIfDNSSECDisabled("Warning: adding Negative Trust Anchor for DNSSEC (addNTA), but dnssec is set to 'off'!");
402 if(why)
403 lci.negAnchors[DNSName(who)] = static_cast<string>(*why);
404 else
405 lci.negAnchors[DNSName(who)] = "";
406 });
407
408 Lua.writeFunction("clearNTA", [&lci](boost::optional<string> who) {
409 warnIfDNSSECDisabled("Warning: removing Negative Trust Anchor for DNSSEC (clearNTA), but dnssec is set to 'off'!");
410 if(who)
411 lci.negAnchors.erase(DNSName(*who));
412 else
413 lci.negAnchors.clear();
414 });
415
416 Lua.writeFunction("readTrustAnchorsFromFile", [&lci](const std::string& fname, const boost::optional<uint32_t> interval) {
417 uint32_t realInterval = 24;
418 if (interval) {
419 realInterval = static_cast<uint32_t>(*interval);
420 }
421 warnIfDNSSECDisabled("Warning: reading Trust Anchors from file (readTrustAnchorsFromFile), but dnssec is set to 'off'!");
422 lci.trustAnchorFileInfo.fname = fname;
423 lci.trustAnchorFileInfo.interval = realInterval;
424 updateTrustAnchorsFromFile(fname, lci.dsAnchors);
425 });
426
427 #if HAVE_PROTOBUF
428 Lua.writeFunction("setProtobufMasks", [&lci](const uint8_t maskV4, uint8_t maskV6) {
429 lci.protobufMaskV4 = maskV4;
430 lci.protobufMaskV6 = maskV6;
431 });
432
433 Lua.writeFunction("protobufServer", [&lci](boost::variant<const std::string, const std::unordered_map<int, std::string>> servers, boost::optional<protobufOptions_t> vars) {
434 if (!lci.protobufExportConfig.enabled) {
435
436 lci.protobufExportConfig.enabled = true;
437
438 try {
439 if (servers.type() == typeid(std::string)) {
440 auto server = boost::get<const std::string>(servers);
441
442 lci.protobufExportConfig.servers.emplace_back(server);
443 }
444 else {
445 auto serversMap = boost::get<const std::unordered_map<int,std::string>>(servers);
446 for (const auto& serverPair : serversMap) {
447 lci.protobufExportConfig.servers.emplace_back(serverPair.second);
448 }
449 }
450
451 parseProtobufOptions(vars, lci.protobufExportConfig);
452 }
453 catch(std::exception& e) {
454 g_log<<Logger::Error<<"Error while adding protobuf logger: "<<e.what()<<endl;
455 }
456 catch(PDNSException& e) {
457 g_log<<Logger::Error<<"Error while adding protobuf logger: "<<e.reason<<endl;
458 }
459 }
460 else {
461 g_log<<Logger::Error<<"Only one protobufServer() directive can be configured, we already have "<<lci.protobufExportConfig.servers.at(0).toString()<<endl;
462 }
463 });
464
465 Lua.writeFunction("outgoingProtobufServer", [&lci](boost::variant<const std::string, const std::unordered_map<int, std::string>> servers, boost::optional<protobufOptions_t> vars) {
466 if (!lci.outgoingProtobufExportConfig.enabled) {
467
468 lci.outgoingProtobufExportConfig.enabled = true;
469
470 try {
471 if (servers.type() == typeid(std::string)) {
472 auto server = boost::get<const std::string>(servers);
473
474 lci.outgoingProtobufExportConfig.servers.emplace_back(server);
475 }
476 else {
477 auto serversMap = boost::get<const std::unordered_map<int,std::string>>(servers);
478 for (const auto& serverPair : serversMap) {
479 lci.outgoingProtobufExportConfig.servers.emplace_back(serverPair.second);
480 }
481 }
482
483 parseProtobufOptions(vars, lci.outgoingProtobufExportConfig);
484 }
485 catch(std::exception& e) {
486 g_log<<Logger::Error<<"Error while starting outgoing protobuf logger: "<<e.what()<<endl;
487 }
488 catch(PDNSException& e) {
489 g_log<<Logger::Error<<"Error while starting outgoing protobuf logger: "<<e.reason<<endl;
490 }
491 }
492 else {
493 g_log<<Logger::Error<<"Only one outgoingProtobufServer() directive can be configured, we already have "<<lci.outgoingProtobufExportConfig.servers.at(0).toString()<<endl;
494 }
495 });
496 #endif
497
498 try {
499 Lua.executeCode(ifs);
500 g_luaconfs.setState(lci);
501 }
502 catch(const LuaContext::ExecutionErrorException& e) {
503 g_log<<Logger::Error<<"Unable to load Lua script from '"+fname+"': ";
504 try {
505 std::rethrow_if_nested(e);
506 } catch(const std::exception& exp) {
507 // exp is the exception that was thrown from inside the lambda
508 g_log << exp.what() << std::endl;
509 }
510 catch(const PDNSException& exp) {
511 // exp is the exception that was thrown from inside the lambda
512 g_log << exp.reason << std::endl;
513 }
514 throw;
515
516 }
517 catch(std::exception& err) {
518 g_log<<Logger::Error<<"Unable to load Lua script from '"+fname+"': "<<err.what()<<endl;
519 throw;
520 }
521
522 }
523
524 void startLuaConfigDelayedThreads(const luaConfigDelayedThreads& delayedThreads, uint64_t generation)
525 {
526 for (const auto& rpzMaster : delayedThreads.rpzMasterThreads) {
527 try {
528 std::thread t(RPZIXFRTracker, std::get<0>(rpzMaster), std::get<1>(rpzMaster), std::get<2>(rpzMaster), std::get<3>(rpzMaster), std::get<4>(rpzMaster), std::get<5>(rpzMaster), std::get<6>(rpzMaster) * 1024 * 1024, std::get<7>(rpzMaster), std::get<8>(rpzMaster), std::get<9>(rpzMaster), std::get<10>(rpzMaster), generation);
529 t.detach();
530 }
531 catch(const std::exception& e) {
532 g_log<<Logger::Error<<"Problem starting RPZIXFRTracker thread: "<<e.what()<<endl;
533 exit(1); // FIXME proper exit code?
534 }
535 catch(const PDNSException& e) {
536 g_log<<Logger::Error<<"Problem starting RPZIXFRTracker thread: "<<e.reason<<endl;
537 exit(1); // FIXME proper exit code?
538 }
539 }
540 }