]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ixfrdist.cc
Merge pull request #15758 from omoerbeek/rec-listen-v6-by-default
[thirdparty/pdns.git] / pdns / ixfrdist.cc
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22 #include "dns.hh"
23 #include "dnsparser.hh"
24 #include <stdexcept>
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 #include <boost/program_options.hpp>
29 #include <arpa/inet.h>
30 #include <sys/types.h>
31 #include <grp.h>
32 #include <pwd.h>
33 #include <sys/stat.h>
34 #include <mutex>
35 #include <thread>
36 #include "threadname.hh"
37 #include <dirent.h>
38 #include <queue>
39 #include <condition_variable>
40 #include <thread>
41 #include <chrono>
42 #include "ixfr.hh"
43 #include "ixfrutils.hh"
44 #include "axfr-retriever.hh"
45 #include "dns_random.hh"
46 #include "sstuff.hh"
47 #include "mplexer.hh"
48 #include "misc.hh"
49 #include "iputils.hh"
50 #include "lock.hh"
51 #include "communicator.hh"
52 #include "query-local-address.hh"
53 #include "logger.hh"
54 #include "ixfrdist-stats.hh"
55 #include "ixfrdist-web.hh"
56 #pragma GCC diagnostic push
57 #pragma GCC diagnostic ignored "-Wshadow"
58 #include <yaml-cpp/yaml.h>
59 #pragma GCC diagnostic pop
60 #include "auth-packetcache.hh"
61 #include "auth-querycache.hh"
62 #include "auth-zonecache.hh"
63
64 /* BEGIN Needed because of deeper dependencies */
65 #include "arguments.hh"
66 #include "statbag.hh"
67 StatBag S;
68 // NOLINTNEXTLINE(readability-identifier-length)
69 AuthPacketCache PC;
70 // NOLINTNEXTLINE(readability-identifier-length)
71 AuthQueryCache QC;
72 AuthZoneCache g_zoneCache;
73
74 ArgvMap &arg()
75 {
76 static ArgvMap theArg;
77 return theArg;
78 }
79 /* END Needed because of deeper dependencies */
80
81 // Allows reading/writing ComboAddresses and ZoneNames in YAML-cpp
82 namespace YAML {
83 template<>
84 struct convert<ComboAddress> {
85 static Node encode(const ComboAddress& rhs) {
86 return Node(rhs.toStringWithPort());
87 }
88 static bool decode(const Node& node, ComboAddress& rhs) {
89 if (!node.IsScalar()) {
90 return false;
91 }
92 try {
93 rhs = ComboAddress(node.as<string>(), 53);
94 return true;
95 } catch(const runtime_error &e) {
96 return false;
97 } catch (const PDNSException &e) {
98 return false;
99 }
100 }
101 };
102
103 template<>
104 struct convert<ZoneName> {
105 static Node encode(const ZoneName& rhs) {
106 return Node(rhs.toStringRootDot());
107 }
108 static bool decode(const Node& node, ZoneName& rhs) {
109 if (!node.IsScalar()) {
110 return false;
111 }
112 try {
113 rhs = ZoneName(node.as<string>());
114 return true;
115 } catch(const runtime_error &e) {
116 return false;
117 } catch (const PDNSException &e) {
118 return false;
119 }
120 }
121 };
122
123 template<>
124 struct convert<Netmask> {
125 static Node encode(const Netmask& rhs) {
126 return Node(rhs.toString());
127 }
128 static bool decode(const Node& node, Netmask& rhs) {
129 if (!node.IsScalar()) {
130 return false;
131 }
132 try {
133 rhs = Netmask(node.as<string>());
134 return true;
135 } catch(const runtime_error &e) {
136 return false;
137 } catch (const PDNSException &e) {
138 return false;
139 }
140 }
141 };
142 } // namespace YAML
143
144 struct ixfrdiff_t {
145 shared_ptr<const SOARecordContent> oldSOA;
146 shared_ptr<const SOARecordContent> newSOA;
147 vector<DNSRecord> removals;
148 vector<DNSRecord> additions;
149 uint32_t oldSOATTL;
150 uint32_t newSOATTL;
151 };
152
153 struct ixfrinfo_t {
154 shared_ptr<const SOARecordContent> soa; // The SOA of the latest AXFR
155 records_t latestAXFR; // The most recent AXFR
156 vector<std::shared_ptr<ixfrdiff_t>> ixfrDiffs;
157 uint32_t soaTTL;
158 };
159
160 // Why a struct? This way we can add more options to a domain in the future
161 struct ixfrdistdomain_t {
162 set<ComboAddress> primaries; // A set so we can do multiple primary addresses in the future
163 std::set<ComboAddress> notify; // Set of addresses to forward NOTIFY to
164 uint32_t maxSOARefresh{0}; // Cap SOA refresh value to the given value in seconds
165 };
166
167 // This contains the configuration for each domain
168 static map<ZoneName, ixfrdistdomain_t> g_domainConfigs;
169
170 // Map domains and their data
171 static LockGuarded<std::map<ZoneName, std::shared_ptr<ixfrinfo_t>>> g_soas;
172
173 // Queue of received NOTIFYs, already verified against their primary IPs
174 // Lazily implemented as a set
175 static LockGuarded<std::set<ZoneName>> g_notifiesReceived;
176
177 // Queue of outgoing NOTIFY
178 static LockGuarded<NotificationQueue> g_notificationQueue;
179
180 // Condition variable for TCP handling
181 static std::condition_variable g_tcpHandlerCV;
182 static std::queue<pair<int, ComboAddress>> g_tcpRequestFDs;
183 static std::mutex g_tcpRequestFDsMutex;
184
185 namespace po = boost::program_options;
186
187 static bool g_exiting = false;
188
189 static NetmaskGroup g_acl; // networks that can QUERY us
190 static NetmaskGroup g_notifySources; // networks (well, IPs) that can NOTIFY us
191 static bool g_compress = false;
192
193 static ixfrdistStats g_stats;
194
195 // g_stats is static, so local to this file. But the webserver needs this info
196 string doGetStats() {
197 return g_stats.getStats();
198 }
199
200 static void handleSignal(int signum) {
201 g_log<<Logger::Notice<<"Got "<<strsignal(signum)<<" signal";
202 if (g_exiting) {
203 g_log<<Logger::Notice<<", this is the second time we were asked to stop, forcefully exiting"<<endl;
204 exit(EXIT_FAILURE);
205 }
206 g_log<<Logger::Notice<<", stopping, this may take a few second due to in-progress transfers and cleanup. Send this signal again to forcefully stop"<<endl;
207 g_exiting = true;
208 }
209
210 static void usage(po::options_description &desc) {
211 cerr << "Usage: ixfrdist [OPTION]..."<<endl;
212 cerr << desc << "\n";
213 }
214
215 // The compiler does not like using rfc1982LessThan in std::sort directly
216 static bool sortSOA(uint32_t i, uint32_t j) {
217 return rfc1982LessThan(i, j);
218 }
219
220 static void cleanUpDomain(const ZoneName& domain, const uint16_t& keep, const string& workdir) {
221 string dir = workdir + "/" + domain.toString();
222 vector<uint32_t> zoneVersions;
223 auto directoryError = pdns::visit_directory(dir, [&zoneVersions]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
224 if (name != "." && name != "..") {
225 try {
226 auto version = pdns::checked_stoi<uint32_t>(std::string(name));
227 zoneVersions.push_back(version);
228 }
229 catch (...) {
230 }
231 }
232 return true;
233 });
234
235 if (directoryError) {
236 return;
237 }
238
239 g_log<<Logger::Info<<"Found "<<zoneVersions.size()<<" versions of "<<domain<<", asked to keep "<<keep<<", ";
240 if (zoneVersions.size() <= keep) {
241 g_log<<Logger::Info<<"not cleaning up"<<endl;
242 return;
243 }
244 g_log<<Logger::Info<<"cleaning up the oldest "<<zoneVersions.size() - keep<<endl;
245
246 // Sort the versions
247 std::sort(zoneVersions.begin(), zoneVersions.end(), sortSOA);
248
249 // And delete all the old ones
250 {
251 // Lock to ensure no one reads this.
252 auto lock = g_soas.lock();
253 for (auto iter = zoneVersions.cbegin(); iter != zoneVersions.cend() - keep; ++iter) {
254 string fname = dir + "/" + std::to_string(*iter);
255 g_log<<Logger::Debug<<"Removing "<<fname<<endl;
256 unlink(fname.c_str());
257 }
258 }
259 }
260
261 static void getSOAFromRecords(const records_t& records, shared_ptr<const SOARecordContent>& soa, uint32_t& soaTTL) {
262 for (const auto& dnsrecord : records) {
263 if (dnsrecord.d_type == QType::SOA) {
264 soa = getRR<SOARecordContent>(dnsrecord);
265 if (soa == nullptr) {
266 throw PDNSException("Unable to determine SOARecordContent from old records");
267 }
268 soaTTL = dnsrecord.d_ttl;
269 return;
270 }
271 }
272 throw PDNSException("No SOA in supplied records");
273 }
274
275 static void makeIXFRDiff(const records_t& from, const records_t& to, std::shared_ptr<ixfrdiff_t>& diff, const shared_ptr<const SOARecordContent>& fromSOA = nullptr, uint32_t fromSOATTL=0, const shared_ptr<const SOARecordContent>& toSOA = nullptr, uint32_t toSOATTL = 0) {
276 set_difference(from.cbegin(), from.cend(), to.cbegin(), to.cend(), back_inserter(diff->removals), from.value_comp());
277 set_difference(to.cbegin(), to.cend(), from.cbegin(), from.cend(), back_inserter(diff->additions), from.value_comp());
278 diff->oldSOA = fromSOA;
279 diff->oldSOATTL = fromSOATTL;
280 if (fromSOA == nullptr) {
281 getSOAFromRecords(from, diff->oldSOA, diff->oldSOATTL);
282 }
283 diff->newSOA = toSOA;
284 diff->newSOATTL = toSOATTL;
285 if (toSOA == nullptr) {
286 getSOAFromRecords(to, diff->newSOA, diff->newSOATTL);
287 }
288 }
289
290 /* you can _never_ alter the content of the resulting shared pointer */
291 static std::shared_ptr<ixfrinfo_t> getCurrentZoneInfo(const ZoneName& domain)
292 {
293 return (*g_soas.lock())[domain];
294 }
295
296 static void updateCurrentZoneInfo(const ZoneName& domain, std::shared_ptr<ixfrinfo_t>& newInfo)
297 {
298 auto soas = g_soas.lock();
299 (*soas)[domain] = newInfo;
300 g_stats.setSOASerial(domain, newInfo->soa->d_st.serial);
301 // FIXME: also report zone size?
302 }
303
304 static void sendNotification(int sock, const ZoneName& domain, const ComboAddress& remote, uint16_t notificationId)
305 {
306 std::vector<std::string> meta;
307 std::vector<uint8_t> packet;
308 DNSPacketWriter packetWriter(packet, domain.operator const DNSName&(), QType::SOA, 1, Opcode::Notify);
309 packetWriter.getHeader()->id = notificationId;
310 packetWriter.getHeader()->aa = true;
311
312 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
313 if (sendto(sock, packet.data(), packet.size(), 0, reinterpret_cast<const struct sockaddr*>(&remote), remote.getSocklen()) < 0) {
314 throw std::runtime_error("Unable to send notify to " + remote.toStringWithPort() + ": " + stringerror());
315 }
316 }
317
318 static void communicatorReceiveNotificationAnswers(const int sock4, const int sock6)
319 {
320 std::set<int> fds = {sock4};
321 if (sock6 > 0) {
322 fds.insert(sock6);
323 }
324 ComboAddress from;
325 std::array<char, 1500> buffer{};
326 int sock{-1};
327
328 // receive incoming notification answers on the nonblocking sockets and take them off the list
329 while (waitForMultiData(fds, 0, 0, &sock) > 0) {
330 Utility::socklen_t fromlen = sizeof(from);
331 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
332 const auto size = recvfrom(sock, buffer.data(), buffer.size(), 0, reinterpret_cast<struct sockaddr*>(&from), &fromlen);
333 if (size < 0) {
334 break;
335 }
336 DNSPacket packet(true);
337 packet.setRemote(&from);
338
339 if (packet.parse(buffer.data(), (size_t)size) < 0) {
340 g_log << Logger::Warning << "Unable to parse SOA notification answer from " << packet.getRemote() << endl;
341 continue;
342 }
343
344 if (packet.d.rcode != 0) {
345 g_log << Logger::Warning << "Received unsuccessful notification report for '" << packet.qdomain << "' from " << from.toStringWithPort() << ", error: " << RCode::to_s(packet.d.rcode) << endl;
346 }
347
348 if (g_notificationQueue.lock()->removeIf(from, packet.d.id, ZoneName(packet.qdomain))) {
349 g_log << Logger::Notice << "Removed from notification list: '" << packet.qdomain << "' to " << from.toStringWithPort() << " " << (packet.d.rcode != 0 ? RCode::to_s(packet.d.rcode) : "(was acknowledged)") << endl;
350 }
351 else {
352 g_log << Logger::Warning << "Received spurious notify answer for '" << packet.qdomain << "' from " << from.toStringWithPort() << endl;
353 }
354 }
355 }
356
357 static void communicatorSendNotifications(const int sock4, const int sock6)
358 {
359 ZoneName domain;
360 string destinationIp;
361 uint16_t notificationId = 0;
362 bool purged{false};
363
364 while (g_notificationQueue.lock()->getOne(domain, destinationIp, &notificationId, purged)) {
365 if (!purged) {
366 ComboAddress remote(destinationIp, 53); // default to 53
367 if (remote.sin4.sin_family == AF_INET) {
368 sendNotification(sock4, domain, remote, notificationId);
369 } else if (sock6 > 0) {
370 sendNotification(sock6, domain, remote, notificationId);
371 } else {
372 g_log << Logger::Warning << "Unable to notify " << destinationIp << " for " << domain << " as v6 support is not enabled" << std::endl;
373 }
374 } else {
375 g_log << Logger::Warning << "Notification for " << domain << " to " << destinationIp << " failed after retries" << std::endl;
376 }
377 }
378 }
379
380 static void communicatorThread()
381 {
382 setThreadName("ixfrdist/communicator");
383 auto sock4 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET, 0), true);
384 auto sock6 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET6, 0), true);
385
386 if (sock4 < 0) {
387 throw std::runtime_error("Unable to create local query socket");
388 }
389 // sock6 can be negative if there is no v6 support, but this is handled later while sending notifications
390
391 while (true) {
392 if (g_exiting) {
393 g_log << Logger::Notice << "Communicator thread stopped" << std::endl;
394 break;
395 }
396 communicatorReceiveNotificationAnswers(sock4, sock6);
397 communicatorSendNotifications(sock4, sock6);
398 std::this_thread::sleep_for(std::chrono::seconds(1));
399 }
400 if (sock4 >= 0) {
401 closesocket(sock4);
402 }
403 if (sock6 >= 0) {
404 closesocket(sock6);
405 }
406 }
407
408 static void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout, const uint16_t& soaRetry, const uint32_t axfrMaxRecords) { // NOLINT(readability-function-cognitive-complexity) 13400 https://github.com/PowerDNS/pdns/issues/13400 Habbie: ixfrdist: reduce complexity
409 setThreadName("ixfrdist/update");
410 std::map<ZoneName, time_t> lastCheck;
411
412 // Initialize the serials we have
413 for (const auto &domainConfig : g_domainConfigs) {
414 ZoneName domain = domainConfig.first;
415 lastCheck[domain] = 0;
416 string dir = workdir + "/" + domain.toString();
417 try {
418 g_log<<Logger::Info<<"Trying to initially load domain "<<domain<<" from disk"<<endl;
419 auto serial = getSerialFromDir(dir);
420 shared_ptr<const SOARecordContent> soa;
421 uint32_t soaTTL{0};
422 {
423 string fname = workdir + "/" + domain.toString() + "/" + std::to_string(serial);
424 loadSOAFromDisk(domain, fname, soa, soaTTL);
425 records_t records;
426 if (soa == nullptr) {
427 g_log<<Logger::Error<<"Could not load SOA from disk for zone "<<domain<<", removing file '"<<fname<<"'"<<endl;
428 unlink(fname.c_str());
429 }
430 loadZoneFromDisk(records, fname, domain);
431 auto zoneInfo = std::make_shared<ixfrinfo_t>();
432 zoneInfo->latestAXFR = std::move(records);
433 zoneInfo->soa = soa;
434 zoneInfo->soaTTL = soaTTL;
435 updateCurrentZoneInfo(domain, zoneInfo);
436 }
437 if (soa != nullptr) {
438 g_log<<Logger::Notice<<"Loaded zone "<<domain<<" with serial "<<soa->d_st.serial<<endl;
439 // Initial cleanup
440 cleanUpDomain(domain, keep, workdir);
441 }
442 } catch (runtime_error &e) {
443 // Most likely, the directory does not exist.
444 g_log<<Logger::Info<<e.what()<<", attempting to create"<<endl;
445 // Attempt to create it, if _that_ fails, there is no hope
446 if (mkdir(dir.c_str(), 0777) == -1 && errno != EEXIST) {
447 g_log<<Logger::Error<<"Could not create '"<<dir<<"': "<<stringerror()<<endl;
448 _exit(EXIT_FAILURE);
449 }
450 }
451 }
452
453 g_log<<Logger::Notice<<"Update Thread started"<<endl;
454
455 while (true) {
456 if (g_exiting) {
457 g_log<<Logger::Notice<<"UpdateThread stopped"<<endl;
458 break;
459 }
460 time_t now = time(nullptr);
461 for (const auto &domainConfig : g_domainConfigs) {
462
463 if (g_exiting) {
464 break;
465 }
466
467 ZoneName domain = domainConfig.first;
468 shared_ptr<const SOARecordContent> current_soa;
469 const auto& zoneInfo = getCurrentZoneInfo(domain);
470 if (zoneInfo != nullptr) {
471 current_soa = zoneInfo->soa;
472 }
473
474 auto& zoneLastCheck = lastCheck[domain];
475 uint32_t refresh = soaRetry; // default if we don't get an update at all
476 if (current_soa != nullptr) {
477 // Check every `refresh` seconds as advertised in the SOA record
478 refresh = current_soa->d_st.refresh;
479 if (domainConfig.second.maxSOARefresh > 0) {
480 // Cap refresh value to the configured one if any
481 refresh = std::min(refresh, domainConfig.second.maxSOARefresh);
482 }
483 }
484
485
486 if (now - zoneLastCheck < refresh && g_notifiesReceived.lock()->erase(domain) == 0) {
487 continue;
488 }
489
490 // TODO Keep track of 'down' primaries
491 set<ComboAddress>::const_iterator it(domainConfig.second.primaries.begin());
492 std::advance(it, dns_random(domainConfig.second.primaries.size()));
493 ComboAddress primary = *it;
494
495 string dir = workdir + "/" + domain.toString();
496 g_log << Logger::Info << "Attempting to retrieve SOA Serial update for '" << domain << "' from '" << primary.toStringWithPort() << "'" << endl;
497 shared_ptr<const SOARecordContent> sr;
498 try {
499 zoneLastCheck = now;
500 g_stats.incrementSOAChecks(domain);
501 auto newSerial = getSerialFromPrimary(primary, domain, sr); // TODO TSIG
502 if(current_soa != nullptr) {
503 g_log << Logger::Info << "Got SOA Serial for " << domain << " from " << primary.toStringWithPort() << ": " << newSerial << ", had Serial: " << current_soa->d_st.serial;
504 if (newSerial == current_soa->d_st.serial) {
505 g_log<<Logger::Info<<", not updating."<<endl;
506 continue;
507 }
508 g_log<<Logger::Info<<", will update."<<endl;
509 }
510 } catch (runtime_error &e) {
511 g_log << Logger::Warning << "Unable to get SOA serial update for '" << domain << "' from primary " << primary.toStringWithPort() << ": " << e.what() << endl;
512 g_stats.incrementSOAChecksFailed(domain);
513 continue;
514 }
515 // Now get the full zone!
516 g_log<<Logger::Info<<"Attempting to receive full zonedata for '"<<domain<<"'"<<endl;
517 ComboAddress local = primary.isIPv4() ? ComboAddress("0.0.0.0") : ComboAddress("::");
518 TSIGTriplet tt;
519
520 // The *new* SOA
521 shared_ptr<const SOARecordContent> soa;
522 uint32_t soaTTL = 0;
523 records_t records;
524 try {
525 AXFRRetriever axfr(primary, domain, tt, &local);
526 uint32_t nrecords=0;
527 Resolver::res_t nop;
528 vector<DNSRecord> chunk;
529 time_t t_start = time(nullptr);
530 time_t axfr_now = time(nullptr);
531 while(axfr.getChunk(nop, &chunk, (axfr_now - t_start + axfrTimeout))) {
532 for(auto& dr : chunk) {
533 if(dr.d_type == QType::TSIG)
534 continue;
535 if(!dr.d_name.isPartOf(domain)) {
536 throw PDNSException("Out-of-zone data received during AXFR of "+domain.toLogString());
537 }
538 dr.d_name.makeUsRelative(domain);
539 records.insert(dr);
540 nrecords++;
541 if (dr.d_type == QType::SOA) {
542 soa = getRR<SOARecordContent>(dr);
543 soaTTL = dr.d_ttl;
544 }
545 }
546 if (axfrMaxRecords != 0 && nrecords > axfrMaxRecords) {
547 throw PDNSException("Received more than " + std::to_string(axfrMaxRecords) + " records in AXFR, aborted");
548 }
549 axfr_now = time(nullptr);
550 if (axfr_now - t_start > axfrTimeout) {
551 g_stats.incrementAXFRFailures(domain);
552 throw PDNSException("Total AXFR time exceeded!");
553 }
554 }
555 if (soa == nullptr) {
556 g_stats.incrementAXFRFailures(domain);
557 g_log<<Logger::Warning<<"No SOA was found in the AXFR of "<<domain<<endl;
558 continue;
559 }
560 g_log<<Logger::Notice<<"Retrieved all zone data for "<<domain<<". Received "<<nrecords<<" records."<<endl;
561 } catch (PDNSException &e) {
562 g_stats.incrementAXFRFailures(domain);
563 g_log<<Logger::Warning<<"Could not retrieve AXFR for '"<<domain<<"': "<<e.reason<<endl;
564 continue;
565 } catch (runtime_error &e) {
566 g_stats.incrementAXFRFailures(domain);
567 g_log<<Logger::Warning<<"Could not retrieve AXFR for zone '"<<domain<<"': "<<e.what()<<endl;
568 continue;
569 }
570
571 try {
572
573 writeZoneToDisk(records, domain, dir);
574 g_log<<Logger::Notice<<"Wrote zonedata for "<<domain<<" with serial "<<soa->d_st.serial<<" to "<<dir<<endl;
575
576 const auto oldZoneInfo = getCurrentZoneInfo(domain);
577 auto ixfrInfo = std::make_shared<ixfrinfo_t>();
578
579 if (oldZoneInfo && !oldZoneInfo->latestAXFR.empty()) {
580 auto diff = std::make_shared<ixfrdiff_t>();
581 ixfrInfo->ixfrDiffs = oldZoneInfo->ixfrDiffs;
582 g_log<<Logger::Debug<<"Calculating diff for "<<domain<<endl;
583 makeIXFRDiff(oldZoneInfo->latestAXFR, records, diff, oldZoneInfo->soa, oldZoneInfo->soaTTL, soa, soaTTL);
584 g_log<<Logger::Debug<<"Calculated diff for "<<domain<<", we had "<<diff->removals.size()<<" removals and "<<diff->additions.size()<<" additions"<<endl;
585 ixfrInfo->ixfrDiffs.push_back(std::move(diff));
586 }
587
588 // Clean up the diffs
589 while (ixfrInfo->ixfrDiffs.size() > keep) {
590 ixfrInfo->ixfrDiffs.erase(ixfrInfo->ixfrDiffs.begin());
591 }
592
593 g_log<<Logger::Debug<<"Zone "<<domain<<" previously contained "<<(oldZoneInfo ? oldZoneInfo->latestAXFR.size() : 0)<<" entries, "<<records.size()<<" now"<<endl;
594 ixfrInfo->latestAXFR = std::move(records);
595 ixfrInfo->soa = std::move(soa);
596 ixfrInfo->soaTTL = soaTTL;
597 updateCurrentZoneInfo(domain, ixfrInfo);
598 } catch (PDNSException &e) {
599 g_stats.incrementAXFRFailures(domain);
600 g_log<<Logger::Warning<<"Could not save zone '"<<domain<<"' to disk: "<<e.reason<<endl;
601 } catch (runtime_error &e) {
602 g_stats.incrementAXFRFailures(domain);
603 g_log<<Logger::Warning<<"Could not save zone '"<<domain<<"' to disk: "<<e.what()<<endl;
604 }
605
606 // Now clean up the directory
607 cleanUpDomain(domain, keep, workdir);
608 } /* for (const auto &domain : domains) */
609 sleep(1);
610 } /* while (true) */
611 } /* updateThread */
612
613 enum class ResponseType {
614 Unknown,
615 ValidQuery,
616 RefusedOpcode,
617 RefusedQuery,
618 EmptyNoError
619 };
620
621 static ResponseType maybeHandleNotify(const MOADNSParser& mdp, const ComboAddress& saddr, const string& logPrefix="") {
622 if (mdp.d_header.opcode != Opcode::Notify) { // NOLINT(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) opcode is 4 bits, this is not a dangerous conversion
623 return ResponseType::Unknown;
624 }
625
626 g_log<<Logger::Info<<logPrefix<<"NOTIFY for "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" "<< Opcode::to_s(mdp.d_header.opcode) <<" from "<<saddr.toStringWithPort()<<endl;
627
628 ZoneName zonename(mdp.d_qname);
629 auto found = g_domainConfigs.find(zonename);
630 if (found == g_domainConfigs.end()) {
631 g_log<<Logger::Info<<("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for notification")<<endl;
632 return ResponseType::RefusedQuery;
633 }
634
635 auto primaries = found->second.primaries;
636
637 bool primaryFound = false;
638
639 for (const auto& primary : primaries) {
640 if (ComboAddress::addressOnlyEqual()(saddr, primary)) {
641 primaryFound = true;
642 break;
643 }
644 }
645
646 if (primaryFound) {
647 g_notifiesReceived.lock()->insert(zonename);
648
649 if (!found->second.notify.empty()) {
650 for (const auto& address : found->second.notify) {
651 g_log << Logger::Debug << logPrefix << "Queuing notification for " << mdp.d_qname << " to " << address.toStringWithPort() << std::endl;
652 g_notificationQueue.lock()->add(zonename, address);
653 }
654 }
655 return ResponseType::EmptyNoError;
656 }
657
658 return ResponseType::RefusedQuery;
659 }
660
661 static ResponseType checkQuery(const MOADNSParser& mdp, const ComboAddress& saddr, const bool udp = true, const string& logPrefix="") {
662 vector<string> info_msg;
663
664 auto ret = ResponseType::ValidQuery;
665
666 g_log<<Logger::Debug<<logPrefix<<"Had "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" query from "<<saddr.toStringWithPort()<<endl;
667
668 if (mdp.d_header.opcode != Opcode::Query) { // NOLINT(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) opcode is 4 bits, this is not a dangerous conversion
669 info_msg.push_back("Opcode is unsupported (" + Opcode::to_s(mdp.d_header.opcode) + "), expected QUERY"); // note that we also emit this for a NOTIFY from a wrong source
670 ret = ResponseType::RefusedOpcode;
671 }
672 else {
673 if (udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR) {
674 info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).toString() + " is not in {SOA,IXFR})");
675 ret = ResponseType::RefusedQuery;
676 }
677
678 if (!udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR && mdp.d_qtype != QType::AXFR) {
679 info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).toString() + " is not in {SOA,IXFR,AXFR})");
680 ret = ResponseType::RefusedQuery;
681 }
682
683 {
684 ZoneName zonename(mdp.d_qname);
685 if (g_domainConfigs.find(zonename) == g_domainConfigs.end()) {
686 info_msg.push_back("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for distribution");
687 ret = ResponseType::RefusedQuery;
688 }
689 else {
690 const auto zoneInfo = getCurrentZoneInfo(zonename);
691 if (zoneInfo == nullptr) {
692 info_msg.emplace_back("Domain has not been transferred yet");
693 ret = ResponseType::RefusedQuery;
694 }
695 }
696 }
697 }
698
699 if (!info_msg.empty()) { // which means ret is not SOA
700 g_log<<Logger::Warning<<logPrefix<<"Refusing "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" "<< Opcode::to_s(mdp.d_header.opcode) <<" from "<<saddr.toStringWithPort();
701 g_log<<Logger::Warning<<": ";
702 bool first = true;
703 for (const auto& s : info_msg) {
704 if (!first) {
705 g_log<<Logger::Warning<<", ";
706 }
707 first = false;
708 g_log<<Logger::Warning<<s;
709 }
710 g_log<<Logger::Warning<<endl;
711 // fall through to return below
712 }
713
714 return ret;
715 }
716
717 /*
718 * Returns a vector<uint8_t> that represents the full empty NOERROR response.
719 * QNAME is read from mdp.
720 */
721 static bool makeEmptyNoErrorPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
722 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
723 pw.getHeader()->opcode = mdp.d_header.opcode;
724 pw.getHeader()->id = mdp.d_header.id;
725 pw.getHeader()->rd = mdp.d_header.rd;
726 pw.getHeader()->qr = 1;
727 pw.getHeader()->aa = 1;
728
729 pw.commit();
730
731 return true;
732 }
733
734 /*
735 * Returns a vector<uint8_t> that represents the full positive response to a SOA
736 * query. QNAME is read from mdp.
737 */
738 static bool makeSOAPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
739
740 auto zoneInfo = getCurrentZoneInfo(ZoneName(mdp.d_qname));
741 if (zoneInfo == nullptr) {
742 return false;
743 }
744
745 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
746 pw.getHeader()->opcode = mdp.d_header.opcode;
747 pw.getHeader()->id = mdp.d_header.id;
748 pw.getHeader()->rd = mdp.d_header.rd;
749 pw.getHeader()->qr = 1;
750 pw.getHeader()->aa = 1;
751
752 pw.startRecord(mdp.d_qname, QType::SOA, zoneInfo->soaTTL);
753 zoneInfo->soa->toPacket(pw);
754 pw.commit();
755
756 return true;
757 }
758
759 /*
760 * Returns a vector<uint8_t> that represents the full REFUSED response to a
761 * query. QNAME and type are read from mdp.
762 */
763 static bool makeRefusedPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
764 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
765 pw.getHeader()->opcode = mdp.d_header.opcode;
766 pw.getHeader()->id = mdp.d_header.id;
767 pw.getHeader()->rd = mdp.d_header.rd;
768 pw.getHeader()->qr = 1;
769 pw.getHeader()->rcode = RCode::Refused;
770
771 return true;
772 }
773
774 /*
775 * Returns a vector<uint8_t> that represents the full NOTIMP response to a
776 * query. QNAME and type are read from mdp.
777 */
778 static bool makeNotimpPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
779 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
780 pw.getHeader()->opcode = mdp.d_header.opcode;
781 pw.getHeader()->id = mdp.d_header.id;
782 pw.getHeader()->rd = mdp.d_header.rd;
783 pw.getHeader()->qr = 1;
784 pw.getHeader()->rcode = RCode::NotImp;
785
786 return true;
787 }
788
789 static vector<uint8_t> getSOAPacket(const MOADNSParser& mdp, const shared_ptr<const SOARecordContent>& soa, uint32_t soaTTL) {
790 vector<uint8_t> packet;
791 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
792 pw.getHeader()->id = mdp.d_header.id;
793 pw.getHeader()->rd = mdp.d_header.rd;
794 pw.getHeader()->qr = 1;
795
796 // Add the first SOA
797 pw.startRecord(mdp.d_qname, QType::SOA, soaTTL);
798 soa->toPacket(pw);
799 pw.commit();
800 return packet;
801 }
802
803 static bool sendPacketOverTCP(int fd, const std::vector<uint8_t>& packet)
804 {
805 char sendBuf[2];
806 sendBuf[0]=packet.size()/256;
807 sendBuf[1]=packet.size()%256;
808
809 writen2(fd, sendBuf, 2);
810 writen2(fd, &packet[0], packet.size());
811 return true;
812 }
813
814 static bool addRecordToWriter(DNSPacketWriter& pw, const DNSName& zoneName, const DNSRecord& record, bool compress)
815 {
816 pw.startRecord(record.d_name + zoneName, record.d_type, record.d_ttl, QClass::IN, DNSResourceRecord::ANSWER, compress);
817 record.getContent()->toPacket(pw);
818 if (pw.size() > 16384) {
819 pw.rollback();
820 return false;
821 }
822 return true;
823 }
824
825 template <typename T> static bool sendRecordsOverTCP(int fd, const MOADNSParser& mdp, const T& records)
826 {
827 vector<uint8_t> packet;
828
829 for (auto it = records.cbegin(); it != records.cend();) {
830 bool recordsAdded = false;
831 packet.clear();
832 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
833 pw.getHeader()->id = mdp.d_header.id;
834 pw.getHeader()->rd = mdp.d_header.rd;
835 pw.getHeader()->qr = 1;
836
837 while (it != records.cend()) {
838 if (it->d_type == QType::SOA) {
839 it++;
840 continue;
841 }
842
843 if (addRecordToWriter(pw, mdp.d_qname, *it, g_compress)) {
844 recordsAdded = true;
845 it++;
846 }
847 else {
848 if (recordsAdded) {
849 pw.commit();
850 sendPacketOverTCP(fd, packet);
851 }
852 if (it == records.cbegin()) {
853 /* something is wrong */
854 return false;
855 }
856
857 break;
858 }
859 }
860
861 if (it == records.cend() && recordsAdded) {
862 pw.commit();
863 sendPacketOverTCP(fd, packet);
864 }
865 }
866
867 return true;
868 }
869
870
871 static bool handleAXFR(int fd, const MOADNSParser& mdp) {
872 /* we get a shared pointer of the zone info that we can't modify, ever.
873 A newer one may arise in the meantime, but this one will stay valid
874 until we release it.
875 */
876
877 ZoneName zonename(mdp.d_qname);
878 g_stats.incrementAXFRinQueries(zonename);
879
880 auto zoneInfo = getCurrentZoneInfo(zonename);
881 if (zoneInfo == nullptr) {
882 return false;
883 }
884
885 shared_ptr<const SOARecordContent> soa = zoneInfo->soa;
886 uint32_t soaTTL = zoneInfo->soaTTL;
887 const records_t& records = zoneInfo->latestAXFR;
888
889 // Initial SOA
890 const auto soaPacket = getSOAPacket(mdp, soa, soaTTL);
891 if (!sendPacketOverTCP(fd, soaPacket)) {
892 return false;
893 }
894
895 if (!sendRecordsOverTCP(fd, mdp, records)) {
896 return false;
897 }
898
899 // Final SOA
900 if (!sendPacketOverTCP(fd, soaPacket)) {
901 return false;
902 }
903
904 return true;
905 }
906
907 /* Produces an IXFR if one can be made according to the rules in RFC 1995 and
908 * creates a SOA or AXFR packet when required by the RFC.
909 */
910 static bool handleIXFR(int fd, const MOADNSParser& mdp, const shared_ptr<const SOARecordContent>& clientSOA) {
911 vector<std::shared_ptr<ixfrdiff_t>> toSend;
912
913 /* we get a shared pointer of the zone info that we can't modify, ever.
914 A newer one may arise in the meantime, but this one will stay valid
915 until we release it.
916 */
917
918 ZoneName zonename(mdp.d_qname);
919 g_stats.incrementIXFRinQueries(zonename);
920
921 auto zoneInfo = getCurrentZoneInfo(zonename);
922 if (zoneInfo == nullptr) {
923 return false;
924 }
925
926 uint32_t ourLatestSerial = zoneInfo->soa->d_st.serial;
927
928 if (rfc1982LessThan(ourLatestSerial, clientSOA->d_st.serial) || ourLatestSerial == clientSOA->d_st.serial) {
929 /* RFC 1995 Section 2
930 * If an IXFR query with the same or newer version number than that of
931 * the server is received, it is replied to with a single SOA record of
932 * the server's current version.
933 */
934 vector<uint8_t> packet;
935 bool ret = makeSOAPacket(mdp, packet);
936 if (ret) {
937 sendPacketOverTCP(fd, packet);
938 }
939 return ret;
940 }
941
942 // as we use push_back in the updater, we know the vector is sorted as oldest first
943 bool shouldAdd = false;
944 // Get all relevant IXFR differences
945 for (const auto& diff : zoneInfo->ixfrDiffs) {
946 if (shouldAdd) {
947 toSend.push_back(diff);
948 continue;
949 }
950 if (diff->oldSOA->d_st.serial == clientSOA->d_st.serial) {
951 toSend.push_back(diff);
952 // Add all consecutive diffs
953 shouldAdd = true;
954 }
955 }
956
957 if (toSend.empty()) {
958 // FIXME: incrementIXFRFallbacks
959 g_log<<Logger::Warning<<"No IXFR available from serial "<<clientSOA->d_st.serial<<" for zone "<<mdp.d_qname<<", attempting to send AXFR"<<endl;
960 return handleAXFR(fd, mdp);
961 }
962
963
964 /* An IXFR packet's ANSWER section looks as follows:
965 * SOA latest_serial C
966
967 First set of changes:
968 * SOA requested_serial A
969 * ... removed records ...
970 * SOA intermediate_serial B
971 * ... added records ...
972
973 Next set of changes:
974 * SOA intermediate_serial B
975 * ... removed records ...
976 * SOA latest_serial C
977 * ... added records ...
978
979 * SOA latest_serial C
980 */
981
982 const auto latestSOAPacket = getSOAPacket(mdp, zoneInfo->soa, zoneInfo->soaTTL);
983 if (!sendPacketOverTCP(fd, latestSOAPacket)) {
984 return false;
985 }
986
987 for (const auto& diff : toSend) {
988 const auto newSOAPacket = getSOAPacket(mdp, diff->newSOA, diff->newSOATTL);
989 const auto oldSOAPacket = getSOAPacket(mdp, diff->oldSOA, diff->oldSOATTL);
990
991 if (!sendPacketOverTCP(fd, oldSOAPacket)) {
992 return false;
993 }
994
995 if (!sendRecordsOverTCP(fd, mdp, diff->removals)) {
996 return false;
997 }
998
999 if (!sendPacketOverTCP(fd, newSOAPacket)) {
1000 return false;
1001 }
1002
1003 if (!sendRecordsOverTCP(fd, mdp, diff->additions)) {
1004 return false;
1005 }
1006 }
1007
1008 if (!sendPacketOverTCP(fd, latestSOAPacket)) {
1009 return false;
1010 }
1011
1012 return true;
1013 }
1014
1015 static bool allowedByACL(const ComboAddress& addr, bool forNotify = false) {
1016 if (forNotify) {
1017 return g_notifySources.match(addr);
1018 }
1019
1020 return g_acl.match(addr);
1021 }
1022
1023 static void handleUDPRequest(int fd, boost::any& /*unused*/)
1024 try
1025 {
1026 // TODO make the buffer-size configurable
1027 char buf[4096];
1028 ComboAddress saddr;
1029 socklen_t fromlen = sizeof(saddr);
1030 int res = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*) &saddr, &fromlen);
1031
1032 if (res == 0) {
1033 g_log<<Logger::Warning<<"Got an empty message from "<<saddr.toStringWithPort()<<endl;
1034 return;
1035 }
1036
1037 if(res < 0) {
1038 auto savedErrno = errno;
1039 g_log<<Logger::Warning<<"Could not read message from "<<saddr.toStringWithPort()<<": "<<strerror(savedErrno)<<endl;
1040 return;
1041 }
1042
1043 if (saddr == ComboAddress("0.0.0.0", 0)) {
1044 g_log<<Logger::Warning<<"Could not determine source of message"<<endl;
1045 return;
1046 }
1047
1048 if (!allowedByACL(saddr, true) && !allowedByACL(saddr, false)) {
1049 g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" did not match any valid query or NOTIFY source, dropping"<<endl;
1050 return;
1051 }
1052
1053 MOADNSParser mdp(true, string(&buf[0], static_cast<size_t>(res)));
1054 vector<uint8_t> packet;
1055
1056 ResponseType respt = ResponseType::Unknown;
1057
1058 if (allowedByACL(saddr, true)) {
1059 respt = maybeHandleNotify(mdp, saddr);
1060 }
1061 else if (!allowedByACL(saddr)) {
1062 g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
1063 return;
1064 }
1065
1066 if (respt == ResponseType::Unknown) {
1067 // query was not handled yet (so not a valid NOTIFY)
1068 respt = checkQuery(mdp, saddr);
1069 }
1070 if (respt == ResponseType::ValidQuery) {
1071 /* RFC 1995 Section 2
1072 * Transport of a query may be by either UDP or TCP. If an IXFR query
1073 * is via UDP, the IXFR server may attempt to reply using UDP if the
1074 * entire response can be contained in a single DNS packet. If the UDP
1075 * reply does not fit, the query is responded to with a single SOA
1076 * record of the server's current version to inform the client that a
1077 * TCP query should be initiated.
1078 *
1079 * Let's not complicate this with IXFR over UDP (and looking if we need to truncate etc).
1080 * Just send the current SOA and let the client try over TCP
1081 */
1082 g_stats.incrementSOAinQueries(ZoneName(mdp.d_qname)); // FIXME: this also counts IXFR queries (but the response is the same as to a SOA query)
1083 makeSOAPacket(mdp, packet);
1084 } else if (respt == ResponseType::EmptyNoError) {
1085 makeEmptyNoErrorPacket(mdp, packet);
1086 } else if (respt == ResponseType::RefusedQuery) {
1087 g_stats.incrementUnknownDomainInQueries(ZoneName(mdp.d_qname));
1088 makeRefusedPacket(mdp, packet);
1089 } else if (respt == ResponseType::RefusedOpcode) {
1090 g_stats.incrementNotImplemented(mdp.d_header.opcode);
1091 makeNotimpPacket(mdp, packet);
1092 }
1093
1094 if(sendto(fd, &packet[0], packet.size(), 0, (struct sockaddr*) &saddr, fromlen) < 0) {
1095 auto savedErrno = errno;
1096 g_log<<Logger::Warning<<"Could not send reply for "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" to "<<saddr.toStringWithPort()<<": "<<strerror(savedErrno)<<endl;
1097 }
1098 return;
1099 }
1100 catch(std::exception& e) {
1101 return;
1102 }
1103
1104
1105 static void handleTCPRequest(int fd, boost::any&) {
1106 ComboAddress saddr;
1107 int cfd = 0;
1108
1109 try {
1110 cfd = SAccept(fd, saddr);
1111 setBlocking(cfd);
1112 } catch(runtime_error &e) {
1113 g_log<<Logger::Error<<e.what()<<endl;
1114 return;
1115 }
1116
1117 if (saddr == ComboAddress("0.0.0.0", 0)) {
1118 g_log<<Logger::Warning<<"Could not determine source of message"<<endl;
1119 close(cfd);
1120 return;
1121 }
1122
1123 // we allow the connection if this is a legit client or a legit NOTIFY source
1124 // need to check per-operation later
1125 if (!allowedByACL(saddr) && !allowedByACL(saddr, true)) {
1126 g_log<<Logger::Warning<<"TCP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
1127 close(cfd);
1128 return;
1129 }
1130
1131 {
1132 std::lock_guard<std::mutex> lg(g_tcpRequestFDsMutex);
1133 g_tcpRequestFDs.push({cfd, saddr});
1134 }
1135 g_tcpHandlerCV.notify_one();
1136 }
1137
1138 /* Thread to handle TCP traffic
1139 */
1140 static void tcpWorker(int tid) {
1141 setThreadName("ixfrdist/tcpWor");
1142 string prefix = "TCP Worker " + std::to_string(tid) + ": ";
1143
1144 while(true) {
1145 g_log<<Logger::Debug<<prefix<<"ready for a new request!"<<endl;
1146 std::unique_lock<std::mutex> lk(g_tcpRequestFDsMutex);
1147 g_tcpHandlerCV.wait(lk, []{return g_tcpRequestFDs.size() || g_exiting ;});
1148 if (g_exiting) {
1149 g_log<<Logger::Debug<<prefix<<"Stopping thread"<<endl;
1150 break;
1151 }
1152 g_log<<Logger::Debug<<prefix<<"Going to handle a query"<<endl;
1153 auto request = g_tcpRequestFDs.front();
1154 g_tcpRequestFDs.pop();
1155 lk.unlock();
1156
1157 int cfd = request.first;
1158 ComboAddress saddr = request.second;
1159
1160 char buf[4096];
1161 ssize_t res;
1162 try {
1163 uint16_t toRead;
1164 readn2(cfd, &toRead, sizeof(toRead));
1165 toRead = std::min(ntohs(toRead), static_cast<uint16_t>(sizeof(buf)));
1166 res = readn2WithTimeout(cfd, &buf, toRead, timeval{2,0});
1167 g_log<<Logger::Debug<<prefix<<"Had message of "<<std::to_string(toRead)<<" bytes from "<<saddr.toStringWithPort()<<endl;
1168 } catch (runtime_error &e) {
1169 g_log<<Logger::Warning<<prefix<<"Could not read message from "<<saddr.toStringWithPort()<<": "<<e.what()<<endl;
1170 close(cfd);
1171 continue;
1172 }
1173
1174 try {
1175 MOADNSParser mdp(true, string(buf, res));
1176
1177 ResponseType respt = ResponseType::Unknown;
1178
1179 // this code is duplicated from the UDP path
1180 if (allowedByACL(saddr, true)) {
1181 respt = maybeHandleNotify(mdp, saddr);
1182 }
1183 else if (!allowedByACL(saddr)) {
1184 close(cfd);
1185 continue;
1186 }
1187
1188 if (respt == ResponseType::Unknown) {
1189 respt = checkQuery(mdp, saddr, false, prefix);
1190 }
1191
1192 if (respt != ResponseType::ValidQuery && respt != ResponseType::EmptyNoError) { // on TCP, we currently do not bother with sending useful errors
1193 close(cfd);
1194 continue;
1195 }
1196
1197 vector<uint8_t> packet;
1198
1199 if (respt == ResponseType::EmptyNoError) {
1200 bool ret = makeEmptyNoErrorPacket(mdp, packet);
1201 if (!ret) {
1202 close(cfd);
1203 continue;
1204 }
1205 sendPacketOverTCP(cfd, packet);
1206 }
1207 else if (mdp.d_qtype == QType::SOA) {
1208 bool ret = makeSOAPacket(mdp, packet);
1209 if (!ret) {
1210 close(cfd);
1211 continue;
1212 }
1213 sendPacketOverTCP(cfd, packet);
1214 }
1215 else if (mdp.d_qtype == QType::AXFR) {
1216 if (!handleAXFR(cfd, mdp)) {
1217 close(cfd);
1218 continue;
1219 }
1220 }
1221 else if (mdp.d_qtype == QType::IXFR) {
1222 /* RFC 1995 section 3:
1223 * The IXFR query packet format is the same as that of a normal DNS
1224 * query, but with the query type being IXFR and the authority section
1225 * containing the SOA record of client's version of the zone.
1226 */
1227 shared_ptr<const SOARecordContent> clientSOA;
1228 for (auto &answer : mdp.d_answers) {
1229 // from dnsparser.hh:
1230 // typedef vector<pair<DNSRecord, uint16_t > > answers_t;
1231 if (answer.d_type == QType::SOA && answer.d_place == DNSResourceRecord::AUTHORITY) {
1232 clientSOA = getRR<SOARecordContent>(answer);
1233 if (clientSOA != nullptr) {
1234 break;
1235 }
1236 }
1237 } /* for (auto const &answer : mdp.d_answers) */
1238
1239 if (clientSOA == nullptr) {
1240 g_log<<Logger::Warning<<prefix<<"IXFR request packet did not contain a SOA record in the AUTHORITY section"<<endl;
1241 close(cfd);
1242 continue;
1243 }
1244
1245 if (!handleIXFR(cfd, mdp, clientSOA)) {
1246 close(cfd);
1247 continue;
1248 }
1249 } /* if (mdp.d_qtype == QType::IXFR) */
1250
1251 shutdown(cfd, 2);
1252 } catch (const MOADNSException &mde) {
1253 g_log<<Logger::Warning<<prefix<<"Could not parse DNS packet from "<<saddr.toStringWithPort()<<": "<<mde.what()<<endl;
1254 } catch (runtime_error &e) {
1255 g_log<<Logger::Warning<<prefix<<"Could not write reply to "<<saddr.toStringWithPort()<<": "<<e.what()<<endl;
1256 }
1257 // bye!
1258 close(cfd);
1259
1260 if (g_exiting) {
1261 break;
1262 }
1263 }
1264 }
1265
1266 /* Parses the configuration file in configpath into config, adding defaults for
1267 * missing parameters (if applicable), returning true if the config file was
1268 * good, false otherwise. Will log all issues with the config
1269 */
1270 static bool parseAndCheckConfig(const string& configpath, YAML::Node& config) {
1271 g_log<<Logger::Info<<"Loading configuration file from "<<configpath<<endl;
1272 try {
1273 config = YAML::LoadFile(configpath);
1274 } catch (const runtime_error &e) {
1275 g_log<<Logger::Error<<"Unable to load configuration file '"<<configpath<<"': "<<e.what()<<endl;
1276 return false;
1277 }
1278
1279 bool retval = true;
1280
1281 if (config["keep"]) {
1282 try {
1283 config["keep"].as<uint16_t>();
1284 } catch (const runtime_error &e) {
1285 g_log<<Logger::Error<<"Unable to read 'keep' value: "<<e.what()<<endl;
1286 retval = false;
1287 }
1288 } else {
1289 config["keep"] = 20;
1290 }
1291
1292 if (config["axfr-max-records"]) {
1293 try {
1294 config["axfr-max-records"].as<uint32_t>();
1295 } catch (const runtime_error &e) {
1296 g_log<<Logger::Error<<"Unable to read 'axfr-max-records' value: "<<e.what()<<endl;
1297 }
1298 } else {
1299 config["axfr-max-records"] = 0;
1300 }
1301
1302 if (config["axfr-timeout"]) {
1303 try {
1304 config["axfr-timeout"].as<uint16_t>();
1305 } catch (const runtime_error &e) {
1306 g_log<<Logger::Error<<"Unable to read 'axfr-timeout' value: "<<e.what()<<endl;
1307 }
1308 } else {
1309 config["axfr-timeout"] = 20;
1310 }
1311
1312 if (config["failed-soa-retry"]) {
1313 try {
1314 config["failed-soa-retry"].as<uint16_t>();
1315 } catch (const runtime_error &e) {
1316 g_log<<Logger::Error<<"Unable to read 'failed-soa-retry' value: "<<e.what()<<endl;
1317 }
1318 } else {
1319 config["failed-soa-retry"] = 30;
1320 }
1321
1322 if (config["tcp-in-threads"]) {
1323 try {
1324 config["tcp-in-threads"].as<uint16_t>();
1325 } catch (const runtime_error &e) {
1326 g_log<<Logger::Error<<"Unable to read 'tcp-in-threads' value: "<<e.what()<<endl;
1327 }
1328 } else {
1329 config["tcp-in-threads"] = 10;
1330 }
1331
1332 if (config["listen"]) {
1333 try {
1334 config["listen"].as<vector<ComboAddress>>();
1335 } catch (const runtime_error &e) {
1336 g_log<<Logger::Error<<"Unable to read 'listen' value: "<<e.what()<<endl;
1337 retval = false;
1338 }
1339 } else {
1340 config["listen"].push_back("127.0.0.1:53");
1341 config["listen"].push_back("[::1]:53");
1342 }
1343
1344 if (config["acl"]) {
1345 try {
1346 config["acl"].as<vector<string>>();
1347 } catch (const runtime_error &e) {
1348 g_log<<Logger::Error<<"Unable to read 'acl' value: "<<e.what()<<endl;
1349 retval = false;
1350 }
1351 } else {
1352 config["acl"].push_back("127.0.0.0/8");
1353 config["acl"].push_back("::1/128");
1354 }
1355
1356 if (config["work-dir"]) {
1357 try {
1358 config["work-dir"].as<string>();
1359 } catch(const runtime_error &e) {
1360 g_log<<Logger::Error<<"Unable to read 'work-dir' value: "<<e.what()<<endl;
1361 retval = false;
1362 }
1363 } else {
1364 char tmp[512];
1365 config["work-dir"] = getcwd(tmp, sizeof(tmp)) ? string(tmp) : "";;
1366 }
1367
1368 if (config["uid"]) {
1369 try {
1370 config["uid"].as<string>();
1371 } catch(const runtime_error &e) {
1372 g_log<<Logger::Error<<"Unable to read 'uid' value: "<<e.what()<<endl;
1373 retval = false;
1374 }
1375 }
1376
1377 if (config["gid"]) {
1378 try {
1379 config["gid"].as<string>();
1380 } catch(const runtime_error &e) {
1381 g_log<<Logger::Error<<"Unable to read 'gid' value: "<<e.what()<<endl;
1382 retval = false;
1383 }
1384 }
1385
1386 if (config["domains"]) {
1387 if (config["domains"].size() == 0) {
1388 g_log<<Logger::Error<<"No domains configured"<<endl;
1389 retval = false;
1390 }
1391 for (auto const &domain : config["domains"]) {
1392 try {
1393 if (!domain["domain"]) {
1394 g_log<<Logger::Error<<"An entry in 'domains' is missing a 'domain' key!"<<endl;
1395 retval = false;
1396 continue;
1397 }
1398 domain["domain"].as<ZoneName>();
1399 } catch (const runtime_error &e) {
1400 g_log<<Logger::Error<<"Unable to read domain '"<<domain["domain"].as<string>()<<"': "<<e.what()<<endl;
1401 }
1402 try {
1403 if (!domain["master"]) {
1404 g_log << Logger::Error << "Domain '" << domain["domain"].as<string>() << "' has no primary configured!" << endl;
1405 retval = false;
1406 continue;
1407 }
1408 domain["master"].as<ComboAddress>();
1409
1410 auto notifySource = domain["master"].as<ComboAddress>();
1411
1412 g_notifySources.addMask(notifySource);
1413 } catch (const runtime_error &e) {
1414 g_log << Logger::Error << "Unable to read domain '" << domain["domain"].as<string>() << "' primary address: " << e.what() << endl;
1415 retval = false;
1416 }
1417 if (domain["max-soa-refresh"]) {
1418 try {
1419 domain["max-soa-refresh"].as<uint32_t>();
1420 } catch (const runtime_error &e) {
1421 g_log<<Logger::Error<<"Unable to read 'max-soa-refresh' value for domain '"<<domain["domain"].as<string>()<<"': "<<e.what()<<endl;
1422 }
1423 }
1424 }
1425 } else {
1426 g_log<<Logger::Error<<"No domains configured"<<endl;
1427 retval = false;
1428 }
1429
1430 if (config["compress"]) {
1431 try {
1432 config["compress"].as<bool>();
1433 }
1434 catch (const runtime_error &e) {
1435 g_log<<Logger::Error<<"Unable to read 'compress' value: "<<e.what()<<endl;
1436 retval = false;
1437 }
1438 }
1439 else {
1440 config["compress"] = false;
1441 }
1442
1443 if (config["webserver-address"]) {
1444 try {
1445 config["webserver-address"].as<ComboAddress>();
1446 }
1447 catch (const runtime_error &e) {
1448 g_log<<Logger::Error<<"Unable to read 'webserver-address' value: "<<e.what()<<endl;
1449 retval = false;
1450 }
1451 }
1452
1453 if (config["webserver-acl"]) {
1454 try {
1455 config["webserver-acl"].as<vector<Netmask>>();
1456 }
1457 catch (const runtime_error &e) {
1458 g_log<<Logger::Error<<"Unable to read 'webserver-acl' value: "<<e.what()<<endl;
1459 retval = false;
1460 }
1461 }
1462
1463 if (config["webserver-loglevel"]) {
1464 try {
1465 config["webserver-loglevel"].as<string>();
1466 }
1467 catch (const runtime_error &e) {
1468 g_log<<Logger::Error<<"Unable to read 'webserver-loglevel' value: "<<e.what()<<endl;
1469 retval = false;
1470 }
1471 }
1472
1473 return retval;
1474 }
1475
1476 struct IXFRDistConfiguration
1477 {
1478 set<int> listeningSockets;
1479 NetmaskGroup wsACL;
1480 ComboAddress wsAddr;
1481 std::string wsLogLevel{"normal"};
1482 std::string workDir;
1483 const struct passwd* userInfo{nullptr};
1484 uint32_t axfrMaxRecords{0};
1485 uint16_t keep{0};
1486 uint16_t axfrTimeout{0};
1487 uint16_t failedSOARetry{0};
1488 uint16_t tcpInThreads{0};
1489 uid_t uid{0};
1490 gid_t gid{0};
1491 bool shouldExit{false};
1492 };
1493
1494 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
1495 static std::optional<IXFRDistConfiguration> parseConfiguration(int argc, char** argv, FDMultiplexer& fdm)
1496 {
1497 IXFRDistConfiguration configuration;
1498 po::variables_map g_vm;
1499 std::string configPath;
1500
1501 try {
1502 po::options_description desc("IXFR distribution tool");
1503 desc.add_options()
1504 ("help", "produce help message")
1505 ("version", "Display the version of ixfrdist")
1506 ("verbose", "Be verbose")
1507 ("debug", "Be even more verbose")
1508 ("config", po::value<string>()->default_value(SYSCONFDIR + string("/ixfrdist.yml")), "Configuration file to use")
1509 ;
1510
1511 po::store(po::command_line_parser(argc, argv).options(desc).run(), g_vm);
1512 po::notify(g_vm);
1513
1514 if (g_vm.count("help") > 0) {
1515 usage(desc);
1516 configuration.shouldExit = true;
1517 return configuration;
1518 }
1519
1520 if (g_vm.count("version") > 0) {
1521 cout<<"ixfrdist "<<VERSION<<endl;
1522 configuration.shouldExit = true;
1523 return configuration;
1524 }
1525
1526 configPath = g_vm["config"].as<string>();
1527 }
1528 catch (const po::error &e) {
1529 g_log<<Logger::Error<<e.what()<<". See `ixfrdist --help` for valid options"<<endl;
1530 return std::nullopt;
1531 }
1532 catch (const std::exception& exp) {
1533 g_log<<Logger::Error<<exp.what()<<". See `ixfrdist --help` for valid options"<<endl;
1534 return std::nullopt;
1535 }
1536
1537 bool had_error = false;
1538
1539 if (g_vm.count("verbose")) {
1540 g_log.setLoglevel(Logger::Info);
1541 g_log.toConsole(Logger::Info);
1542 }
1543
1544 if (g_vm.count("debug") > 0) {
1545 g_log.setLoglevel(Logger::Debug);
1546 g_log.toConsole(Logger::Debug);
1547 }
1548
1549 g_log<<Logger::Notice<<"IXFR distributor version "<<VERSION<<" starting up!"<<endl;
1550
1551 try {
1552 YAML::Node config;
1553 if (!parseAndCheckConfig(configPath, config)) {
1554 // parseAndCheckConfig already logged whatever was wrong
1555 return std::nullopt;
1556 }
1557
1558 /* From hereon out, we known that all the values in config are valid. */
1559
1560 for (auto const &domain : config["domains"]) {
1561 set<ComboAddress> s;
1562 s.insert(domain["master"].as<ComboAddress>());
1563 g_domainConfigs[domain["domain"].as<ZoneName>()].primaries = s;
1564 if (domain["max-soa-refresh"].IsDefined()) {
1565 g_domainConfigs[domain["domain"].as<ZoneName>()].maxSOARefresh = domain["max-soa-refresh"].as<uint32_t>();
1566 }
1567 if (domain["notify"].IsDefined()) {
1568 auto& listset = g_domainConfigs[domain["domain"].as<ZoneName>()].notify;
1569 if (domain["notify"].IsScalar()) {
1570 auto remote = domain["notify"].as<std::string>();
1571 try {
1572 listset.emplace(remote, 53);
1573 }
1574 catch (PDNSException& e) {
1575 g_log << Logger::Error << "Unparseable IP in notify directive " << remote << ". Error: " << e.reason << endl;
1576 }
1577 } else if (domain["notify"].IsSequence()) {
1578 for (const auto& entry: domain["notify"]) {
1579 auto remote = entry.as<std::string>();
1580 try {
1581 listset.emplace(remote, 53);
1582 }
1583 catch (PDNSException& e) {
1584 g_log << Logger::Error << "Unparseable IP in notify directive " << remote << ". Error: " << e.reason << endl;
1585 }
1586 }
1587 }
1588 }
1589 g_stats.registerDomain(domain["domain"].as<ZoneName>());
1590 }
1591
1592 for (const auto &addr : config["acl"].as<vector<string>>()) {
1593 try {
1594 g_acl.addMask(addr);
1595 }
1596 catch (const std::exception& exp) {
1597 g_log<<Logger::Error<<exp.what()<<endl;
1598 had_error = true;
1599 }
1600 catch (const NetmaskException &e) {
1601 g_log<<Logger::Error<<e.reason<<endl;
1602 had_error = true;
1603 }
1604 }
1605
1606 try {
1607 g_log<<Logger::Notice<<"ACL set to "<<g_acl.toString()<<"."<<endl;
1608 }
1609 catch (const std::exception& exp) {
1610 g_log<<Logger::Error<<"Error printing ACL: "<<exp.what()<<endl;
1611 }
1612
1613 g_log<<Logger::Notice<<"NOTIFY accepted from "<<g_notifySources.toString()<<"."<<endl;
1614
1615 if (config["compress"].IsDefined()) {
1616 g_compress = config["compress"].as<bool>();
1617 if (g_compress) {
1618 g_log<<Logger::Notice<<"Record compression is enabled."<<endl;
1619 }
1620 }
1621
1622 for (const auto& addr : config["listen"].as<vector<ComboAddress>>()) {
1623 for (const auto& stype : {SOCK_DGRAM, SOCK_STREAM}) {
1624 try {
1625 int s = SSocket(addr.sin4.sin_family, stype, 0);
1626 setNonBlocking(s);
1627 setReuseAddr(s);
1628 if (addr.isIPv6()) {
1629 int one = 1;
1630 (void)setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
1631 }
1632
1633 SBind(s, addr);
1634 if (stype == SOCK_STREAM) {
1635 SListen(s, 30); // TODO make this configurable
1636 }
1637 fdm.addReadFD(s, stype == SOCK_DGRAM ? handleUDPRequest : handleTCPRequest);
1638 configuration.listeningSockets.insert(s);
1639 }
1640 catch (const runtime_error& exp) {
1641 g_log<<Logger::Error<<exp.what()<<endl;
1642 had_error = true;
1643 continue;
1644 }
1645 catch (const PDNSException& exp) {
1646 g_log<<Logger::Error<<exp.reason<<endl;
1647 had_error = true;
1648 continue;
1649 }
1650 }
1651 }
1652
1653 if (config["gid"].IsDefined()) {
1654 bool gidParsed = false;
1655 auto gid = config["gid"].as<string>();
1656 try {
1657 configuration.gid = pdns::checked_stoi<gid_t>(gid);
1658 gidParsed = true;
1659 }
1660 catch (const std::exception& e) {
1661 configuration.gid = 0;
1662 }
1663 if (!gidParsed) {
1664 //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
1665 const struct group *gr = getgrnam(gid.c_str());
1666 if (gr == nullptr) {
1667 g_log<<Logger::Error<<"Can not determine group-id for gid "<<gid<<endl;
1668 had_error = true;
1669 } else {
1670 configuration.gid = gr->gr_gid;
1671 }
1672 }
1673 }
1674
1675 if (config["webserver-address"].IsDefined()) {
1676 configuration.wsAddr = config["webserver-address"].as<ComboAddress>();
1677
1678 try {
1679 configuration.wsACL.addMask("127.0.0.0/8");
1680 configuration.wsACL.addMask("::1/128");
1681
1682 if (config["webserver-acl"].IsDefined()) {
1683 configuration.wsACL.clear();
1684 for (const auto &acl : config["webserver-acl"].as<vector<Netmask>>()) {
1685 configuration.wsACL.addMask(acl);
1686 }
1687 }
1688 }
1689 catch (const NetmaskException& ne) {
1690 g_log<<Logger::Error<<"Could not set the webserver ACL: "<<ne.reason<<endl;
1691 had_error = true;
1692 }
1693 catch (const std::exception& exp) {
1694 g_log<<Logger::Error<<"Could not set the webserver ACL: "<<exp.what()<<endl;
1695 had_error = true;
1696 }
1697
1698 if (config["webserver-loglevel"]) {
1699 configuration.wsLogLevel = config["webserver-loglevel"].as<string>();
1700 }
1701 }
1702
1703 if (config["uid"].IsDefined()) {
1704 bool uidParsed = false;
1705 auto uid = config["uid"].as<string>();
1706 try {
1707 configuration.uid = pdns::checked_stoi<uid_t>(uid);
1708 uidParsed = true;
1709 }
1710 catch (const std::exception& e) {
1711 configuration.uid = 0;
1712 }
1713 if (!uidParsed) {
1714 //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
1715 const struct passwd *pw = getpwnam(uid.c_str());
1716 if (pw == nullptr) {
1717 g_log<<Logger::Error<<"Can not determine user-id for uid "<<uid<<endl;
1718 had_error = true;
1719 } else {
1720 configuration.uid = pw->pw_uid;
1721 uidParsed = true;
1722 }
1723 //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
1724 }
1725 if (uidParsed) {
1726 configuration.userInfo = getpwuid(configuration.uid);
1727 }
1728 }
1729
1730 configuration.workDir = config["work-dir"].as<string>();
1731 configuration.keep = config["keep"].as<uint16_t>();
1732 configuration.axfrTimeout = config["axfr-timeout"].as<uint16_t>();
1733 configuration.failedSOARetry = config["failed-soa-retry"].as<uint16_t>();
1734 configuration.axfrMaxRecords = config["axfr-max-records"].as<uint32_t>();
1735 configuration.tcpInThreads = config["tcp-in-threads"].as<uint16_t>();
1736
1737 if (had_error) {
1738 return std::nullopt;
1739 }
1740 return configuration;
1741 }
1742 catch (const YAML::Exception& exp) {
1743 had_error = true;
1744 g_log<<Logger::Error<<"Got an exception while applying our configuration: "<<exp.msg<<endl;
1745 return std::nullopt;
1746 }
1747 }
1748
1749 int main(int argc, char** argv) {
1750 bool had_error = false;
1751 std::optional<IXFRDistConfiguration> configuration{std::nullopt};
1752 std::unique_ptr<FDMultiplexer> fdm{nullptr};
1753
1754 try {
1755 g_log.setLoglevel(Logger::Notice);
1756 g_log.toConsole(Logger::Notice);
1757 g_log.setPrefixed(true);
1758 g_log.disableSyslog(true);
1759 g_log.setTimestamps(false);
1760
1761 fdm = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
1762 if (!fdm) {
1763 g_log<<Logger::Error<<"Could not enable a multiplexer for the listen sockets!"<<endl;
1764 return EXIT_FAILURE;
1765 }
1766
1767 configuration = parseConfiguration(argc, argv, *fdm);
1768 if (!configuration) {
1769 // We have already sent the errors to stderr, just die
1770 return EXIT_FAILURE;
1771 }
1772
1773 if (configuration->shouldExit) {
1774 return EXIT_SUCCESS;
1775 }
1776 }
1777 catch (const YAML::Exception& exp) {
1778 had_error = true;
1779 g_log<<Logger::Error<<"Got an exception while processing our configuration: "<<exp.msg<<endl;
1780 }
1781
1782 try {
1783 if (configuration->gid != 0) {
1784 g_log<<Logger::Notice<<"Dropping effective group-id to "<<configuration->gid<<endl;
1785 if (setgid(configuration->gid) < 0) {
1786 g_log<<Logger::Error<<"Could not set group id to "<<configuration->gid<<": "<<stringerror()<<endl;
1787 had_error = true;
1788 }
1789 }
1790
1791 // It all starts here
1792 signal(SIGTERM, handleSignal);
1793 signal(SIGINT, handleSignal);
1794 //NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
1795 signal(SIGPIPE, SIG_IGN);
1796
1797 // Launch the webserver!
1798 try {
1799 std::thread(&IXFRDistWebServer::go, IXFRDistWebServer(configuration->wsAddr, configuration->wsACL, configuration->wsLogLevel)).detach();
1800 }
1801 catch (const std::exception& exp) {
1802 g_log<<Logger::Error<<"Unable to start webserver: "<<exp.what()<<endl;
1803 had_error = true;
1804 }
1805 catch (const PDNSException &e) {
1806 g_log<<Logger::Error<<"Unable to start webserver: "<<e.reason<<endl;
1807 had_error = true;
1808 }
1809
1810 if (configuration->uid != 0) {
1811 if (configuration->userInfo == nullptr) {
1812 if (setgroups(0, nullptr) < 0) {
1813 g_log<<Logger::Error<<"Unable to drop supplementary gids: "<<stringerror()<<endl;
1814 had_error = true;
1815 }
1816 } else {
1817 if (initgroups(configuration->userInfo->pw_name, configuration->gid) < 0) {
1818 g_log<<Logger::Error<<"Unable to set supplementary groups: "<<stringerror()<<endl;
1819 had_error = true;
1820 }
1821 }
1822
1823 g_log<<Logger::Notice<<"Dropping effective user-id to "<<configuration->uid<<endl;
1824 if (setuid(configuration->uid) < 0) {
1825 g_log<<Logger::Error<<"Could not set user id to "<<configuration->uid<<": "<<stringerror()<<endl;
1826 had_error = true;
1827 }
1828 }
1829
1830 if (had_error) {
1831 return EXIT_FAILURE;
1832 }
1833 }
1834 catch (const YAML::Exception& exp) {
1835 had_error = true;
1836 g_log<<Logger::Error<<"Got an exception while applying our configuration: "<<exp.msg<<endl;
1837 }
1838
1839 try {
1840 // Init the things we need
1841 reportAllTypes();
1842
1843 std::thread ut(updateThread,
1844 configuration->workDir,
1845 configuration->keep,
1846 configuration->axfrTimeout,
1847 configuration->failedSOARetry,
1848 configuration->axfrMaxRecords);
1849 std::thread communicator(communicatorThread);
1850
1851 vector<std::thread> tcpHandlers;
1852 tcpHandlers.reserve(configuration->tcpInThreads);
1853 for (size_t i = 0; i < tcpHandlers.capacity(); ++i) {
1854 tcpHandlers.push_back(std::thread(tcpWorker, i));
1855 }
1856
1857 struct timeval now;
1858 for (;;) {
1859 gettimeofday(&now, 0);
1860 fdm->run(&now);
1861 if (g_exiting) {
1862 g_log<<Logger::Debug<<"Closing listening sockets"<<endl;
1863 for (const int& fd : configuration->listeningSockets) {
1864 try {
1865 closesocket(fd);
1866 } catch (const PDNSException &e) {
1867 g_log<<Logger::Error<<e.reason<<endl;
1868 }
1869 }
1870 break;
1871 }
1872 }
1873
1874 g_log<<Logger::Debug<<"Waiting for all threads to stop"<<endl;
1875 g_tcpHandlerCV.notify_all();
1876 ut.join();
1877 communicator.join();
1878 for (auto &t : tcpHandlers) {
1879 t.join();
1880 }
1881 g_log<<Logger::Notice<<"IXFR distributor stopped"<<endl;
1882 }
1883 catch (const YAML::Exception& exp) {
1884 had_error = true;
1885 g_log<<Logger::Error<<"Got an exception: "<<exp.msg<<endl;
1886 }
1887
1888 return had_error ? EXIT_FAILURE : EXIT_SUCCESS;
1889 }