]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ixfrdist.cc
gather a few more stats
[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 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include <boost/program_options.hpp>
26 #include <arpa/inet.h>
27 #include <sys/types.h>
28 #include <grp.h>
29 #include <pwd.h>
30 #include <sys/stat.h>
31 #include <mutex>
32 #include <thread>
33 #include "threadname.hh"
34 #include <dirent.h>
35 #include <queue>
36 #include <condition_variable>
37 #include "ixfr.hh"
38 #include "ixfrutils.hh"
39 #include "resolver.hh"
40 #include "dns_random.hh"
41 #include "sstuff.hh"
42 #include "mplexer.hh"
43 #include "misc.hh"
44 #include "iputils.hh"
45 #include "logger.hh"
46 #include "ixfrdist-stats.hh"
47 #include <yaml-cpp/yaml.h>
48
49 /* BEGIN Needed because of deeper dependencies */
50 #include "arguments.hh"
51 #include "statbag.hh"
52 StatBag S;
53
54 ArgvMap &arg()
55 {
56 static ArgvMap theArg;
57 return theArg;
58 }
59 /* END Needed because of deeper dependencies */
60
61 // Allows reading/writing ComboAddresses and DNSNames in YAML-cpp
62 namespace YAML {
63 template<>
64 struct convert<ComboAddress> {
65 static Node encode(const ComboAddress& rhs) {
66 return Node(rhs.toStringWithPort());
67 }
68 static bool decode(const Node& node, ComboAddress& rhs) {
69 if (!node.IsScalar()) {
70 return false;
71 }
72 try {
73 rhs = ComboAddress(node.as<string>(), 53);
74 return true;
75 } catch(const runtime_error &e) {
76 return false;
77 } catch (const PDNSException &e) {
78 return false;
79 }
80 }
81 };
82
83 template<>
84 struct convert<DNSName> {
85 static Node encode(const DNSName& rhs) {
86 return Node(rhs.toStringRootDot());
87 }
88 static bool decode(const Node& node, DNSName& rhs) {
89 if (!node.IsScalar()) {
90 return false;
91 }
92 try {
93 rhs = DNSName(node.as<string>());
94 return true;
95 } catch(const runtime_error &e) {
96 return false;
97 } catch (const PDNSException &e) {
98 return false;
99 }
100 }
101 };
102 } // namespace YAML
103
104 struct ixfrdiff_t {
105 shared_ptr<SOARecordContent> oldSOA;
106 shared_ptr<SOARecordContent> newSOA;
107 vector<DNSRecord> removals;
108 vector<DNSRecord> additions;
109 };
110
111 struct ixfrinfo_t {
112 shared_ptr<SOARecordContent> soa; // The SOA of the latest AXFR
113 records_t latestAXFR; // The most recent AXFR
114 vector<std::shared_ptr<ixfrdiff_t>> ixfrDiffs;
115 };
116
117 // Why a struct? This way we can add more options to a domain in the future
118 struct ixfrdistdomain_t {
119 set<ComboAddress> masters; // A set so we can do multiple master addresses in the future
120 };
121
122 // This contains the configuration for each domain
123 static map<DNSName, ixfrdistdomain_t> g_domainConfigs;
124
125 // Map domains and their data
126 static std::map<DNSName, std::shared_ptr<ixfrinfo_t>> g_soas;
127 static std::mutex g_soas_mutex;
128
129 // Condition variable for TCP handling
130 static std::condition_variable g_tcpHandlerCV;
131 static std::queue<pair<int, ComboAddress>> g_tcpRequestFDs;
132 static std::mutex g_tcpRequestFDsMutex;
133
134 namespace po = boost::program_options;
135
136 static bool g_exiting = false;
137
138 static NetmaskGroup g_acl;
139 static bool g_compress = false;
140
141 static ixfrdistStats g_stats;
142
143 static void handleSignal(int signum) {
144 g_log<<Logger::Notice<<"Got "<<strsignal(signum)<<" signal";
145 if (g_exiting) {
146 g_log<<Logger::Notice<<", this is the second time we were asked to stop, forcefully exiting"<<endl;
147 exit(EXIT_FAILURE);
148 }
149 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;
150 g_exiting = true;
151 }
152
153 static void usage(po::options_description &desc) {
154 cerr << "Usage: ixfrdist [OPTION]..."<<endl;
155 cerr << desc << "\n";
156 }
157
158 // The compiler does not like using rfc1982LessThan in std::sort directly
159 static bool sortSOA(uint32_t i, uint32_t j) {
160 return rfc1982LessThan(i, j);
161 }
162
163 static void cleanUpDomain(const DNSName& domain, const uint16_t& keep, const string& workdir) {
164 string dir = workdir + "/" + domain.toString();
165 DIR *dp;
166 dp = opendir(dir.c_str());
167 if (dp == nullptr) {
168 return;
169 }
170 vector<uint32_t> zoneVersions;
171 struct dirent *d;
172 while ((d = readdir(dp)) != nullptr) {
173 if(!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) {
174 continue;
175 }
176 zoneVersions.push_back(std::stoi(d->d_name));
177 }
178 closedir(dp);
179 g_log<<Logger::Info<<"Found "<<zoneVersions.size()<<" versions of "<<domain<<", asked to keep "<<keep<<", ";
180 if (zoneVersions.size() <= keep) {
181 g_log<<Logger::Info<<"not cleaning up"<<endl;
182 return;
183 }
184 g_log<<Logger::Info<<"cleaning up the oldest "<<zoneVersions.size() - keep<<endl;
185
186 // Sort the versions
187 std::sort(zoneVersions.begin(), zoneVersions.end(), sortSOA);
188
189 // And delete all the old ones
190 {
191 // Lock to ensure no one reads this.
192 std::lock_guard<std::mutex> guard(g_soas_mutex);
193 for (auto iter = zoneVersions.cbegin(); iter != zoneVersions.cend() - keep; ++iter) {
194 string fname = dir + "/" + std::to_string(*iter);
195 g_log<<Logger::Debug<<"Removing "<<fname<<endl;
196 unlink(fname.c_str());
197 }
198 }
199 }
200
201 static shared_ptr<SOARecordContent> getSOAFromRecords(const records_t& records) {
202 for (const auto& dnsrecord : records) {
203 if (dnsrecord.d_type == QType::SOA) {
204 auto soa = getRR<SOARecordContent>(dnsrecord);
205 if (soa == nullptr) {
206 throw PDNSException("Unable to determine SOARecordContent from old records");
207 }
208 return soa;
209 }
210 }
211 throw PDNSException("No SOA in supplied records");
212 }
213
214 static void makeIXFRDiff(const records_t& from, const records_t& to, std::shared_ptr<ixfrdiff_t>& diff, const shared_ptr<SOARecordContent>& fromSOA = nullptr, const shared_ptr<SOARecordContent>& toSOA = nullptr) {
215 set_difference(from.cbegin(), from.cend(), to.cbegin(), to.cend(), back_inserter(diff->removals), from.value_comp());
216 set_difference(to.cbegin(), to.cend(), from.cbegin(), from.cend(), back_inserter(diff->additions), from.value_comp());
217 diff->oldSOA = fromSOA;
218 if (fromSOA == nullptr) {
219 diff->oldSOA = getSOAFromRecords(from);
220 }
221 diff->newSOA = toSOA;
222 if (toSOA == nullptr) {
223 diff->newSOA = getSOAFromRecords(to);
224 }
225 }
226
227 /* you can _never_ alter the content of the resulting shared pointer */
228 static std::shared_ptr<ixfrinfo_t> getCurrentZoneInfo(const DNSName& domain)
229 {
230 std::lock_guard<std::mutex> guard(g_soas_mutex);
231 return g_soas[domain];
232 }
233
234 static void updateCurrentZoneInfo(const DNSName& domain, std::shared_ptr<ixfrinfo_t>& newInfo)
235 {
236 std::lock_guard<std::mutex> guard(g_soas_mutex);
237 g_soas[domain] = newInfo;
238 g_stats.setSOASerial(domain, newInfo->soa->d_st.serial);
239 // FIXME: also report zone size?
240 }
241
242 void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout, const uint16_t& soaRetry) {
243 setThreadName("ixfrdist/update");
244 std::map<DNSName, time_t> lastCheck;
245
246 // Initialize the serials we have
247 for (const auto &domainConfig : g_domainConfigs) {
248 DNSName domain = domainConfig.first;
249 lastCheck[domain] = 0;
250 string dir = workdir + "/" + domain.toString();
251 try {
252 g_log<<Logger::Info<<"Trying to initially load domain "<<domain<<" from disk"<<endl;
253 auto serial = getSerialsFromDir(dir);
254 shared_ptr<SOARecordContent> soa;
255 {
256 string fname = workdir + "/" + domain.toString() + "/" + std::to_string(serial);
257 loadSOAFromDisk(domain, fname, soa);
258 records_t records;
259 if (soa != nullptr) {
260 loadZoneFromDisk(records, fname, domain);
261 }
262 auto zoneInfo = std::make_shared<ixfrinfo_t>();
263 zoneInfo->latestAXFR = std::move(records);
264 zoneInfo->soa = soa;
265 updateCurrentZoneInfo(domain, zoneInfo);
266 }
267 if (soa != nullptr) {
268 g_log<<Logger::Notice<<"Loaded zone "<<domain<<" with serial "<<soa->d_st.serial<<endl;
269 // Initial cleanup
270 cleanUpDomain(domain, keep, workdir);
271 }
272 } catch (runtime_error &e) {
273 // Most likely, the directory does not exist.
274 g_log<<Logger::Info<<e.what()<<", attempting to create"<<endl;
275 // Attempt to create it, if _that_ fails, there is no hope
276 if (mkdir(dir.c_str(), 0777) == -1 && errno != EEXIST) {
277 g_log<<Logger::Error<<"Could not create '"<<dir<<"': "<<strerror(errno)<<endl;
278 exit(EXIT_FAILURE);
279 }
280 }
281 }
282
283 g_log<<Logger::Notice<<"Update Thread started"<<endl;
284
285 while (true) {
286 cout<<g_stats.getStats()<<endl;
287 if (g_exiting) {
288 g_log<<Logger::Notice<<"UpdateThread stopped"<<endl;
289 break;
290 }
291 time_t now = time(nullptr);
292 for (const auto &domainConfig : g_domainConfigs) {
293
294 if (g_exiting) {
295 break;
296 }
297
298 DNSName domain = domainConfig.first;
299 shared_ptr<SOARecordContent> current_soa;
300 const auto& zoneInfo = getCurrentZoneInfo(domain);
301 if (zoneInfo != nullptr) {
302 current_soa = zoneInfo->soa;
303 }
304
305 auto& zoneLastCheck = lastCheck[domain];
306 if ((current_soa != nullptr && now - zoneLastCheck < current_soa->d_st.refresh) || // Only check if we have waited `refresh` seconds
307 (current_soa == nullptr && now - zoneLastCheck < soaRetry)) { // Or if we could not get an update at all still, every 30 seconds
308 continue;
309 }
310
311 // TODO Keep track of 'down' masters
312 set<ComboAddress>::const_iterator it(domainConfig.second.masters.begin());
313 std::advance(it, random() % domainConfig.second.masters.size());
314 ComboAddress master = *it;
315
316 string dir = workdir + "/" + domain.toString();
317 g_log<<Logger::Info<<"Attempting to retrieve SOA Serial update for '"<<domain<<"' from '"<<master.toStringWithPort()<<"'"<<endl;
318 shared_ptr<SOARecordContent> sr;
319 try {
320 zoneLastCheck = now;
321 g_stats.incrementSOAChecks(domain);
322 auto newSerial = getSerialFromMaster(master, domain, sr); // TODO TSIG
323 if(current_soa != nullptr) {
324 g_log<<Logger::Info<<"Got SOA Serial for "<<domain<<" from "<<master.toStringWithPort()<<": "<< newSerial<<", had Serial: "<<current_soa->d_st.serial;
325 if (newSerial == current_soa->d_st.serial) {
326 g_log<<Logger::Info<<", not updating."<<endl;
327 continue;
328 }
329 g_log<<Logger::Info<<", will update."<<endl;
330 }
331 } catch (runtime_error &e) {
332 g_log<<Logger::Warning<<"Unable to get SOA serial update for '"<<domain<<"' from master "<<master.toStringWithPort()<<": "<<e.what()<<endl;
333 g_stats.incrementSOAChecksFailed(domain);
334 continue;
335 }
336 // Now get the full zone!
337 g_log<<Logger::Info<<"Attempting to receive full zonedata for '"<<domain<<"'"<<endl;
338 ComboAddress local = master.isIPv4() ? ComboAddress("0.0.0.0") : ComboAddress("::");
339 TSIGTriplet tt;
340
341 // The *new* SOA
342 shared_ptr<SOARecordContent> soa;
343 records_t records;
344 try {
345 AXFRRetriever axfr(master, domain, tt, &local);
346 unsigned int nrecords=0;
347 Resolver::res_t nop;
348 vector<DNSRecord> chunk;
349 time_t t_start = time(nullptr);
350 time_t axfr_now = time(nullptr);
351 while(axfr.getChunk(nop, &chunk, (axfr_now - t_start + axfrTimeout))) {
352 for(auto& dr : chunk) {
353 if(dr.d_type == QType::TSIG)
354 continue;
355 if(!dr.d_name.isPartOf(domain)) {
356 throw PDNSException("Out-of-zone data received during AXFR of "+domain.toLogString());
357 }
358 dr.d_name.makeUsRelative(domain);
359 records.insert(dr);
360 nrecords++;
361 if (dr.d_type == QType::SOA) {
362 soa = getRR<SOARecordContent>(dr);
363 }
364 }
365 axfr_now = time(nullptr);
366 if (axfr_now - t_start > axfrTimeout) {
367 g_stats.incrementAXFRFailures(domain);
368 throw PDNSException("Total AXFR time exceeded!");
369 }
370 }
371 if (soa == nullptr) {
372 g_stats.incrementAXFRFailures(domain);
373 g_log<<Logger::Warning<<"No SOA was found in the AXFR of "<<domain<<endl;
374 continue;
375 }
376 g_log<<Logger::Notice<<"Retrieved all zone data for "<<domain<<". Received "<<nrecords<<" records."<<endl;
377 } catch (PDNSException &e) {
378 g_stats.incrementAXFRFailures(domain);
379 g_log<<Logger::Warning<<"Could not retrieve AXFR for '"<<domain<<"': "<<e.reason<<endl;
380 continue;
381 } catch (runtime_error &e) {
382 g_stats.incrementAXFRFailures(domain);
383 g_log<<Logger::Warning<<"Could not retrieve AXFR for zone '"<<domain<<"': "<<e.what()<<endl;
384 continue;
385 }
386
387 try {
388
389 writeZoneToDisk(records, domain, dir);
390 g_log<<Logger::Notice<<"Wrote zonedata for "<<domain<<" with serial "<<soa->d_st.serial<<" to "<<dir<<endl;
391
392 const auto oldZoneInfo = getCurrentZoneInfo(domain);
393 auto zoneInfo = std::make_shared<ixfrinfo_t>();
394
395 if (oldZoneInfo && !oldZoneInfo->latestAXFR.empty()) {
396 auto diff = std::make_shared<ixfrdiff_t>();
397 zoneInfo->ixfrDiffs = oldZoneInfo->ixfrDiffs;
398 g_log<<Logger::Debug<<"Calculating diff for "<<domain<<endl;
399 makeIXFRDiff(oldZoneInfo->latestAXFR, records, diff, oldZoneInfo->soa, soa);
400 g_log<<Logger::Debug<<"Calculated diff for "<<domain<<", we had "<<diff->removals.size()<<" removals and "<<diff->additions.size()<<" additions"<<endl;
401 zoneInfo->ixfrDiffs.push_back(std::move(diff));
402 }
403
404 // Clean up the diffs
405 while (zoneInfo->ixfrDiffs.size() > keep) {
406 zoneInfo->ixfrDiffs.erase(zoneInfo->ixfrDiffs.begin());
407 }
408
409 g_log<<Logger::Debug<<"Zone "<<domain<<" previously contained "<<(oldZoneInfo ? oldZoneInfo->latestAXFR.size() : 0)<<" entries, "<<records.size()<<" now"<<endl;
410 zoneInfo->latestAXFR = std::move(records);
411 zoneInfo->soa = soa;
412 updateCurrentZoneInfo(domain, zoneInfo);
413 } catch (PDNSException &e) {
414 g_stats.incrementAXFRFailures(domain);
415 g_log<<Logger::Warning<<"Could not save zone '"<<domain<<"' to disk: "<<e.reason<<endl;
416 } catch (runtime_error &e) {
417 g_stats.incrementAXFRFailures(domain);
418 g_log<<Logger::Warning<<"Could not save zone '"<<domain<<"' to disk: "<<e.what()<<endl;
419 }
420
421 // Now clean up the directory
422 cleanUpDomain(domain, keep, workdir);
423 } /* for (const auto &domain : domains) */
424 sleep(1);
425 } /* while (true) */
426 } /* updateThread */
427
428 static bool checkQuery(const MOADNSParser& mdp, const ComboAddress& saddr, const bool udp = true, const string& logPrefix="") {
429 vector<string> info_msg;
430
431 g_log<<Logger::Debug<<logPrefix<<"Had "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).getName()<<" query from "<<saddr.toStringWithPort()<<endl;
432
433 if (udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR) {
434 info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).getName() + " is not in {SOA,IXFR})");
435 }
436
437 if (!udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR && mdp.d_qtype != QType::AXFR) {
438 info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).getName() + " is not in {SOA,IXFR,AXFR})");
439 }
440
441 {
442 if (g_domainConfigs.find(mdp.d_qname) == g_domainConfigs.end()) {
443 info_msg.push_back("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for distribution");
444 }
445 else {
446 const auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
447 if (zoneInfo == nullptr) {
448 info_msg.push_back("Domain has not been transferred yet");
449 }
450 }
451 }
452
453 if (!info_msg.empty()) {
454 g_log<<Logger::Warning<<logPrefix<<"Refusing "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).getName()<<" query from "<<saddr.toStringWithPort();
455 g_log<<Logger::Warning<<": ";
456 bool first = true;
457 for (const auto& s : info_msg) {
458 if (!first) {
459 g_log<<Logger::Warning<<", ";
460 }
461 first = false;
462 g_log<<Logger::Warning<<s;
463 }
464 g_log<<Logger::Warning<<endl;
465 return false;
466 }
467
468 return true;
469 }
470
471 /*
472 * Returns a vector<uint8_t> that represents the full positive response to a SOA
473 * query. QNAME is read from mdp.
474 */
475 static bool makeSOAPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
476
477 auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
478 if (zoneInfo == nullptr) {
479 return false;
480 }
481
482 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
483 pw.getHeader()->id = mdp.d_header.id;
484 pw.getHeader()->rd = mdp.d_header.rd;
485 pw.getHeader()->qr = 1;
486
487 pw.startRecord(mdp.d_qname, QType::SOA);
488 zoneInfo->soa->toPacket(pw);
489 pw.commit();
490
491 return true;
492 }
493
494 /*
495 * Returns a vector<uint8_t> that represents the full REFUSED response to a
496 * query. QNAME and type are read from mdp.
497 */
498 static bool makeRefusedPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
499 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
500 pw.getHeader()->id = mdp.d_header.id;
501 pw.getHeader()->rd = mdp.d_header.rd;
502 pw.getHeader()->qr = 1;
503 pw.getHeader()->rcode = RCode::Refused;
504
505 return true;
506 }
507
508 static vector<uint8_t> getSOAPacket(const MOADNSParser& mdp, const shared_ptr<SOARecordContent>& soa) {
509 vector<uint8_t> packet;
510 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
511 pw.getHeader()->id = mdp.d_header.id;
512 pw.getHeader()->rd = mdp.d_header.rd;
513 pw.getHeader()->qr = 1;
514
515 // Add the first SOA
516 pw.startRecord(mdp.d_qname, QType::SOA);
517 soa->toPacket(pw);
518 pw.commit();
519 return packet;
520 }
521
522 static bool sendPacketOverTCP(int fd, const std::vector<uint8_t>& packet)
523 {
524 char sendBuf[2];
525 sendBuf[0]=packet.size()/256;
526 sendBuf[1]=packet.size()%256;
527
528 ssize_t send = writen2(fd, sendBuf, 2);
529 send += writen2(fd, &packet[0], packet.size());
530 return true;
531 }
532
533 static bool addRecordToWriter(DNSPacketWriter& pw, const DNSName& zoneName, const DNSRecord& record, bool compress)
534 {
535 pw.startRecord(record.d_name + zoneName, record.d_type, record.d_ttl, QClass::IN, DNSResourceRecord::ANSWER, compress);
536 record.d_content->toPacket(pw);
537 if (pw.size() > 65535) {
538 pw.rollback();
539 return false;
540 }
541 return true;
542 }
543
544 template <typename T> static bool sendRecordsOverTCP(int fd, const MOADNSParser& mdp, const T& records)
545 {
546 vector<uint8_t> packet;
547
548 for (auto it = records.cbegin(); it != records.cend();) {
549 bool recordsAdded = false;
550 packet.clear();
551 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
552 pw.getHeader()->id = mdp.d_header.id;
553 pw.getHeader()->rd = mdp.d_header.rd;
554 pw.getHeader()->qr = 1;
555
556 while (it != records.cend()) {
557 if (it->d_type == QType::SOA) {
558 it++;
559 continue;
560 }
561
562 if (addRecordToWriter(pw, mdp.d_qname, *it, g_compress)) {
563 recordsAdded = true;
564 it++;
565 }
566 else {
567 if (recordsAdded) {
568 pw.commit();
569 sendPacketOverTCP(fd, packet);
570 }
571 if (it == records.cbegin()) {
572 /* something is wrong */
573 return false;
574 }
575
576 break;
577 }
578 }
579
580 if (it == records.cend() && recordsAdded) {
581 pw.commit();
582 sendPacketOverTCP(fd, packet);
583 }
584 }
585
586 return true;
587 }
588
589
590 static bool handleAXFR(int fd, const MOADNSParser& mdp) {
591 /* we get a shared pointer of the zone info that we can't modify, ever.
592 A newer one may arise in the meantime, but this one will stay valid
593 until we release it.
594 */
595
596 g_stats.incrementAXFRinQueries(mdp.d_qname);
597
598 auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
599 if (zoneInfo == nullptr) {
600 return false;
601 }
602
603 shared_ptr<SOARecordContent> soa = zoneInfo->soa;
604 const records_t& records = zoneInfo->latestAXFR;
605
606 // Initial SOA
607 const auto soaPacket = getSOAPacket(mdp, soa);
608 if (!sendPacketOverTCP(fd, soaPacket)) {
609 return false;
610 }
611
612 if (!sendRecordsOverTCP(fd, mdp, records)) {
613 return false;
614 }
615
616 // Final SOA
617 if (!sendPacketOverTCP(fd, soaPacket)) {
618 return false;
619 }
620
621 return true;
622 }
623
624 /* Produces an IXFR if one can be made according to the rules in RFC 1995 and
625 * creates a SOA or AXFR packet when required by the RFC.
626 */
627 static bool handleIXFR(int fd, const ComboAddress& destination, const MOADNSParser& mdp, const shared_ptr<SOARecordContent>& clientSOA) {
628 vector<std::shared_ptr<ixfrdiff_t>> toSend;
629
630 /* we get a shared pointer of the zone info that we can't modify, ever.
631 A newer one may arise in the meantime, but this one will stay valid
632 until we release it.
633 */
634
635 g_stats.incrementIXFRinQueries(mdp.d_qname);
636
637 auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
638 if (zoneInfo == nullptr) {
639 return false;
640 }
641
642 uint32_t ourLatestSerial = zoneInfo->soa->d_st.serial;
643
644 if (rfc1982LessThan(ourLatestSerial, clientSOA->d_st.serial) || ourLatestSerial == clientSOA->d_st.serial) {
645 /* RFC 1995 Section 2
646 * If an IXFR query with the same or newer version number than that of
647 * the server is received, it is replied to with a single SOA record of
648 * the server's current version.
649 */
650 vector<uint8_t> packet;
651 bool ret = makeSOAPacket(mdp, packet);
652 if (ret) {
653 sendPacketOverTCP(fd, packet);
654 }
655 return ret;
656 }
657
658 // as we use push_back in the updater, we know the vector is sorted as oldest first
659 bool shouldAdd = false;
660 // Get all relevant IXFR differences
661 for (const auto& diff : zoneInfo->ixfrDiffs) {
662 if (shouldAdd) {
663 toSend.push_back(diff);
664 continue;
665 }
666 if (diff->oldSOA->d_st.serial == clientSOA->d_st.serial) {
667 toSend.push_back(diff);
668 // Add all consecutive diffs
669 shouldAdd = true;
670 }
671 }
672
673 if (toSend.empty()) {
674 // FIXME: incrementIXFRFallbacks
675 g_log<<Logger::Warning<<"No IXFR available from serial "<<clientSOA->d_st.serial<<" for zone "<<mdp.d_qname<<", attempting to send AXFR"<<endl;
676 return handleAXFR(fd, mdp);
677 }
678
679 std::vector<std::vector<uint8_t>> packets;
680 for (const auto& diff : toSend) {
681 /* An IXFR packet's ANSWER section looks as follows:
682 * SOA new_serial
683 * SOA old_serial
684 * ... removed records ...
685 * SOA new_serial
686 * ... added records ...
687 * SOA new_serial
688 */
689
690 const auto newSOAPacket = getSOAPacket(mdp, diff->newSOA);
691 const auto oldSOAPacket = getSOAPacket(mdp, diff->oldSOA);
692
693 if (!sendPacketOverTCP(fd, newSOAPacket)) {
694 return false;
695 }
696
697 if (!sendPacketOverTCP(fd, oldSOAPacket)) {
698 return false;
699 }
700
701 if (!sendRecordsOverTCP(fd, mdp, diff->removals)) {
702 return false;
703 }
704
705 if (!sendPacketOverTCP(fd, newSOAPacket)) {
706 return false;
707 }
708
709 if (!sendRecordsOverTCP(fd, mdp, diff->additions)) {
710 return false;
711 }
712
713 if (!sendPacketOverTCP(fd, newSOAPacket)) {
714 return false;
715 }
716 }
717
718 return true;
719 }
720
721 static bool allowedByACL(const ComboAddress& addr) {
722 return g_acl.match(addr);
723 }
724
725 static void handleUDPRequest(int fd, boost::any&) {
726 // TODO make the buffer-size configurable
727 char buf[4096];
728 ComboAddress saddr;
729 socklen_t fromlen = sizeof(saddr);
730 int res = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*) &saddr, &fromlen);
731
732 if (res == 0) {
733 g_log<<Logger::Warning<<"Got an empty message from "<<saddr.toStringWithPort()<<endl;
734 return;
735 }
736
737 if(res < 0) {
738 auto savedErrno = errno;
739 g_log<<Logger::Warning<<"Could not read message from "<<saddr.toStringWithPort()<<": "<<strerror(savedErrno)<<endl;
740 return;
741 }
742
743 if (saddr == ComboAddress("0.0.0.0", 0)) {
744 g_log<<Logger::Warning<<"Could not determine source of message"<<endl;
745 return;
746 }
747
748 if (!allowedByACL(saddr)) {
749 g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
750 return;
751 }
752
753 MOADNSParser mdp(true, string(buf, res));
754 vector<uint8_t> packet;
755 if (checkQuery(mdp, saddr)) {
756 /* RFC 1995 Section 2
757 * Transport of a query may be by either UDP or TCP. If an IXFR query
758 * is via UDP, the IXFR server may attempt to reply using UDP if the
759 * entire response can be contained in a single DNS packet. If the UDP
760 * reply does not fit, the query is responded to with a single SOA
761 * record of the server's current version to inform the client that a
762 * TCP query should be initiated.
763 *
764 * Let's not complicate this with IXFR over UDP (and looking if we need to truncate etc).
765 * Just send the current SOA and let the client try over TCP
766 */
767 g_stats.incrementSOAinQueries(mdp.d_qname); // FIXME: this also counts IXFR queries (but the response is the same as to a SOA query)
768 makeSOAPacket(mdp, packet);
769 } else {
770 makeRefusedPacket(mdp, packet);
771 }
772
773 if(sendto(fd, &packet[0], packet.size(), 0, (struct sockaddr*) &saddr, fromlen) < 0) {
774 auto savedErrno = errno;
775 g_log<<Logger::Warning<<"Could not send reply for "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).getName()<<" to "<<saddr.toStringWithPort()<<": "<<strerror(savedErrno)<<endl;
776 }
777 return;
778 }
779
780 static void handleTCPRequest(int fd, boost::any&) {
781 ComboAddress saddr;
782 int cfd = 0;
783
784 try {
785 cfd = SAccept(fd, saddr);
786 setBlocking(cfd);
787 } catch(runtime_error &e) {
788 g_log<<Logger::Error<<e.what()<<endl;
789 return;
790 }
791
792 if (saddr == ComboAddress("0.0.0.0", 0)) {
793 g_log<<Logger::Warning<<"Could not determine source of message"<<endl;
794 close(cfd);
795 return;
796 }
797
798 if (!allowedByACL(saddr)) {
799 g_log<<Logger::Warning<<"TCP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
800 close(cfd);
801 return;
802 }
803
804 {
805 std::lock_guard<std::mutex> lg(g_tcpRequestFDsMutex);
806 g_tcpRequestFDs.push({cfd, saddr});
807 }
808 g_tcpHandlerCV.notify_one();
809 }
810
811 /* Thread to handle TCP traffic
812 */
813 static void tcpWorker(int tid) {
814 setThreadName("ixfrdist/tcpWor");
815 string prefix = "TCP Worker " + std::to_string(tid) + ": ";
816
817 while(true) {
818 g_log<<Logger::Debug<<prefix<<"ready for a new request!"<<endl;
819 std::unique_lock<std::mutex> lk(g_tcpRequestFDsMutex);
820 g_tcpHandlerCV.wait(lk, []{return g_tcpRequestFDs.size() || g_exiting ;});
821 if (g_exiting) {
822 g_log<<Logger::Debug<<prefix<<"Stopping thread"<<endl;
823 break;
824 }
825 g_log<<Logger::Debug<<prefix<<"Going to handle a query"<<endl;
826 auto request = g_tcpRequestFDs.front();
827 g_tcpRequestFDs.pop();
828 lk.unlock();
829
830 int cfd = request.first;
831 ComboAddress saddr = request.second;
832
833 char buf[4096];
834 ssize_t res;
835 try {
836 uint16_t toRead;
837 readn2(cfd, &toRead, sizeof(toRead));
838 toRead = std::min(ntohs(toRead), static_cast<uint16_t>(sizeof(buf)));
839 res = readn2WithTimeout(cfd, &buf, toRead, 2);
840 g_log<<Logger::Debug<<prefix<<"Had message of "<<std::to_string(toRead)<<" bytes from "<<saddr.toStringWithPort()<<endl;
841 } catch (runtime_error &e) {
842 g_log<<Logger::Warning<<prefix<<"Could not read message from "<<saddr.toStringWithPort()<<": "<<e.what()<<endl;
843 close(cfd);
844 continue;
845 }
846
847 try {
848 MOADNSParser mdp(true, string(buf, res));
849
850 if (!checkQuery(mdp, saddr, false, prefix)) {
851 close(cfd);
852 continue;
853 }
854
855 if (mdp.d_qtype == QType::SOA) {
856 vector<uint8_t> packet;
857 bool ret = makeSOAPacket(mdp, packet);
858 if (!ret) {
859 close(cfd);
860 continue;
861 }
862 sendPacketOverTCP(cfd, packet);
863 }
864 else if (mdp.d_qtype == QType::AXFR) {
865 if (!handleAXFR(cfd, mdp)) {
866 close(cfd);
867 continue;
868 }
869 }
870 else if (mdp.d_qtype == QType::IXFR) {
871 /* RFC 1995 section 3:
872 * The IXFR query packet format is the same as that of a normal DNS
873 * query, but with the query type being IXFR and the authority section
874 * containing the SOA record of client's version of the zone.
875 */
876 shared_ptr<SOARecordContent> clientSOA;
877 for (auto &answer : mdp.d_answers) {
878 // from dnsparser.hh:
879 // typedef vector<pair<DNSRecord, uint16_t > > answers_t;
880 if (answer.first.d_type == QType::SOA && answer.first.d_place == DNSResourceRecord::AUTHORITY) {
881 clientSOA = getRR<SOARecordContent>(answer.first);
882 if (clientSOA != nullptr) {
883 break;
884 }
885 }
886 } /* for (auto const &answer : mdp.d_answers) */
887
888 if (clientSOA == nullptr) {
889 g_log<<Logger::Warning<<prefix<<"IXFR request packet did not contain a SOA record in the AUTHORITY section"<<endl;
890 close(cfd);
891 continue;
892 }
893
894 if (!handleIXFR(cfd, saddr, mdp, clientSOA)) {
895 close(cfd);
896 continue;
897 }
898 } /* if (mdp.d_qtype == QType::IXFR) */
899
900 shutdown(cfd, 2);
901 } catch (const MOADNSException &mde) {
902 g_log<<Logger::Warning<<prefix<<"Could not parse DNS packet from "<<saddr.toStringWithPort()<<": "<<mde.what()<<endl;
903 } catch (runtime_error &e) {
904 g_log<<Logger::Warning<<prefix<<"Could not write reply to "<<saddr.toStringWithPort()<<": "<<e.what()<<endl;
905 }
906 // bye!
907 close(cfd);
908
909 if (g_exiting) {
910 break;
911 }
912 }
913 }
914
915 /* Parses the configuration file in configpath into config, adding defaults for
916 * missing parameters (if applicable), returning true if the config file was
917 * good, false otherwise. Will log all issues with the config
918 */
919 static bool parseAndCheckConfig(const string& configpath, YAML::Node& config) {
920 g_log<<Logger::Info<<"Loading configuration file from "<<configpath<<endl;
921 try {
922 config = YAML::LoadFile(configpath);
923 } catch (const runtime_error &e) {
924 g_log<<Logger::Error<<"Unable to load configuration file '"<<configpath<<"': "<<e.what()<<endl;
925 return false;
926 }
927
928 bool retval = true;
929
930 if (config["keep"]) {
931 try {
932 config["keep"].as<uint16_t>();
933 } catch (const runtime_error &e) {
934 g_log<<Logger::Error<<"Unable to read 'keep' value: "<<e.what()<<endl;
935 retval = false;
936 }
937 } else {
938 config["keep"] = 20;
939 }
940
941 if (config["axfr-timeout"]) {
942 try {
943 config["axfr-timeout"].as<uint16_t>();
944 } catch (const runtime_error &e) {
945 g_log<<Logger::Error<<"Unable to read 'axfr-timeout' value: "<<e.what()<<endl;
946 }
947 } else {
948 config["axfr-timeout"] = 20;
949 }
950
951 if (config["failed-soa-retry"]) {
952 try {
953 config["failed-soa-retry"].as<uint16_t>();
954 } catch (const runtime_error &e) {
955 g_log<<Logger::Error<<"Unable to read 'failed-soa-retry' value: "<<e.what()<<endl;
956 }
957 } else {
958 config["failed-soa-retry"] = 30;
959 }
960
961 if (config["tcp-in-threads"]) {
962 try {
963 config["tcp-in-threads"].as<uint16_t>();
964 } catch (const runtime_error &e) {
965 g_log<<Logger::Error<<"Unable to read 'tcp-in-threads' value: "<<e.what()<<endl;
966 }
967 } else {
968 config["tcp-in-threads"] = 10;
969 }
970
971 if (config["listen"]) {
972 try {
973 config["listen"].as<vector<ComboAddress>>();
974 } catch (const runtime_error &e) {
975 g_log<<Logger::Error<<"Unable to read 'listen' value: "<<e.what()<<endl;
976 retval = false;
977 }
978 } else {
979 config["listen"].push_back("127.0.0.1:53");
980 config["listen"].push_back("[::1]:53");
981 }
982
983 if (config["acl"]) {
984 try {
985 config["acl"].as<vector<string>>();
986 } catch (const runtime_error &e) {
987 g_log<<Logger::Error<<"Unable to read 'acl' value: "<<e.what()<<endl;
988 retval = false;
989 }
990 } else {
991 config["acl"].push_back("127.0.0.0/8");
992 config["acl"].push_back("::1/128");
993 }
994
995 if (config["work-dir"]) {
996 try {
997 config["work-dir"].as<string>();
998 } catch(const runtime_error &e) {
999 g_log<<Logger::Error<<"Unable to read 'work-dir' value: "<<e.what()<<endl;
1000 retval = false;
1001 }
1002 } else {
1003 char tmp[512];
1004 config["work-dir"] = getcwd(tmp, sizeof(tmp)) ? string(tmp) : "";;
1005 }
1006
1007 if (config["uid"]) {
1008 try {
1009 config["uid"].as<string>();
1010 } catch(const runtime_error &e) {
1011 g_log<<Logger::Error<<"Unable to read 'uid' value: "<<e.what()<<endl;
1012 retval = false;
1013 }
1014 }
1015
1016 if (config["gid"]) {
1017 try {
1018 config["gid"].as<string>();
1019 } catch(const runtime_error &e) {
1020 g_log<<Logger::Error<<"Unable to read 'gid' value: "<<e.what()<<endl;
1021 retval = false;
1022 }
1023 }
1024
1025 if (config["domains"]) {
1026 if (config["domains"].size() == 0) {
1027 g_log<<Logger::Error<<"No domains configured"<<endl;
1028 retval = false;
1029 }
1030 for (auto const &domain : config["domains"]) {
1031 try {
1032 if (!domain["domain"]) {
1033 g_log<<Logger::Error<<"An entry in 'domains' is missing a 'domain' key!"<<endl;
1034 retval = false;
1035 continue;
1036 }
1037 domain["domain"].as<DNSName>();
1038 } catch (const runtime_error &e) {
1039 g_log<<Logger::Error<<"Unable to read domain '"<<domain["domain"].as<string>()<<"': "<<e.what()<<endl;
1040 }
1041 try {
1042 if (!domain["master"]) {
1043 g_log<<Logger::Error<<"Domain '"<<domain["domain"].as<string>()<<"' has no master configured!"<<endl;
1044 retval = false;
1045 continue;
1046 }
1047 domain["master"].as<ComboAddress>();
1048 } catch (const runtime_error &e) {
1049 g_log<<Logger::Error<<"Unable to read domain '"<<domain["domain"].as<string>()<<"' master address: "<<e.what()<<endl;
1050 retval = false;
1051 }
1052 }
1053 } else {
1054 g_log<<Logger::Error<<"No domains configured"<<endl;
1055 retval = false;
1056 }
1057
1058 if (config["compress"]) {
1059 try {
1060 config["compress"].as<bool>();
1061 }
1062 catch (const runtime_error &e) {
1063 g_log<<Logger::Error<<"Unable to read 'compress' value: "<<e.what()<<endl;
1064 retval = false;
1065 }
1066 }
1067 else {
1068 config["compress"] = false;
1069 }
1070
1071 return retval;
1072 }
1073
1074 int main(int argc, char** argv) {
1075 g_log.setLoglevel(Logger::Notice);
1076 g_log.toConsole(Logger::Notice);
1077 g_log.setPrefixed(true);
1078 g_log.disableSyslog(true);
1079 g_log.setTimestamps(false);
1080 po::variables_map g_vm;
1081 try {
1082 po::options_description desc("IXFR distribution tool");
1083 desc.add_options()
1084 ("help", "produce help message")
1085 ("version", "Display the version of ixfrdist")
1086 ("verbose", "Be verbose")
1087 ("debug", "Be even more verbose")
1088 ("config", po::value<string>()->default_value(SYSCONFDIR + string("/ixfrdist.yml")), "Configuration file to use")
1089 ;
1090
1091 po::store(po::command_line_parser(argc, argv).options(desc).run(), g_vm);
1092 po::notify(g_vm);
1093
1094 if (g_vm.count("help") > 0) {
1095 usage(desc);
1096 return EXIT_SUCCESS;
1097 }
1098
1099 if (g_vm.count("version") > 0) {
1100 cout<<"ixfrdist "<<VERSION<<endl;
1101 return EXIT_SUCCESS;
1102 }
1103 } catch (po::error &e) {
1104 g_log<<Logger::Error<<e.what()<<". See `ixfrdist --help` for valid options"<<endl;
1105 return(EXIT_FAILURE);
1106 }
1107
1108 bool had_error = false;
1109
1110 if (g_vm.count("verbose")) {
1111 g_log.setLoglevel(Logger::Info);
1112 g_log.toConsole(Logger::Info);
1113 }
1114
1115 if (g_vm.count("debug") > 0) {
1116 g_log.setLoglevel(Logger::Debug);
1117 g_log.toConsole(Logger::Debug);
1118 }
1119
1120 g_log<<Logger::Notice<<"IXFR distributor version "<<VERSION<<" starting up!"<<endl;
1121
1122 YAML::Node config;
1123 if (!parseAndCheckConfig(g_vm["config"].as<string>(), config)) {
1124 // parseAndCheckConfig already logged whatever was wrong
1125 return EXIT_FAILURE;
1126 }
1127
1128 /* From hereon out, we known that all the values in config are valid. */
1129
1130 for (auto const &domain : config["domains"]) {
1131 set<ComboAddress> s;
1132 s.insert(domain["master"].as<ComboAddress>());
1133 g_domainConfigs[domain["domain"].as<DNSName>()].masters = s;
1134 g_stats.registerDomain(domain["domain"].as<DNSName>());
1135 }
1136
1137 for (const auto &addr : config["acl"].as<vector<string>>()) {
1138 try {
1139 g_acl.addMask(addr);
1140 } catch (const NetmaskException &e) {
1141 g_log<<Logger::Error<<e.reason<<endl;
1142 had_error = true;
1143 }
1144 }
1145 g_log<<Logger::Notice<<"ACL set to "<<g_acl.toString()<<"."<<endl;
1146
1147 if (config["compress"]) {
1148 g_compress = config["compress"].as<bool>();
1149 if (g_compress) {
1150 g_log<<Logger::Notice<<"Record compression is enabled."<<endl;
1151 }
1152 }
1153
1154 FDMultiplexer* fdm = FDMultiplexer::getMultiplexerSilent();
1155 if (fdm == nullptr) {
1156 g_log<<Logger::Error<<"Could not enable a multiplexer for the listen sockets!"<<endl;
1157 return EXIT_FAILURE;
1158 }
1159
1160 set<int> allSockets;
1161 for (const auto& addr : config["listen"].as<vector<ComboAddress>>()) {
1162 for (const auto& stype : {SOCK_DGRAM, SOCK_STREAM}) {
1163 try {
1164 int s = SSocket(addr.sin4.sin_family, stype, 0);
1165 setNonBlocking(s);
1166 setReuseAddr(s);
1167 SBind(s, addr);
1168 if (stype == SOCK_STREAM) {
1169 SListen(s, 30); // TODO make this configurable
1170 }
1171 fdm->addReadFD(s, stype == SOCK_DGRAM ? handleUDPRequest : handleTCPRequest);
1172 allSockets.insert(s);
1173 } catch(runtime_error &e) {
1174 g_log<<Logger::Error<<e.what()<<endl;
1175 had_error = true;
1176 continue;
1177 }
1178 }
1179 }
1180
1181 int newgid = 0;
1182
1183 if (config["gid"]) {
1184 string gid = config["gid"].as<string>();
1185 if (!(newgid = atoi(gid.c_str()))) {
1186 struct group *gr = getgrnam(gid.c_str());
1187 if (gr == nullptr) {
1188 g_log<<Logger::Error<<"Can not determine group-id for gid "<<gid<<endl;
1189 had_error = true;
1190 } else {
1191 newgid = gr->gr_gid;
1192 }
1193 }
1194 g_log<<Logger::Notice<<"Dropping effective group-id to "<<newgid<<endl;
1195 if (setgid(newgid) < 0) {
1196 g_log<<Logger::Error<<"Could not set group id to "<<newgid<<": "<<stringerror()<<endl;
1197 had_error = true;
1198 }
1199 }
1200
1201 int newuid = 0;
1202
1203 if (config["uid"]) {
1204 string uid = config["uid"].as<string>();
1205 if (!(newuid = atoi(uid.c_str()))) {
1206 struct passwd *pw = getpwnam(uid.c_str());
1207 if (pw == nullptr) {
1208 g_log<<Logger::Error<<"Can not determine user-id for uid "<<uid<<endl;
1209 had_error = true;
1210 } else {
1211 newuid = pw->pw_uid;
1212 }
1213 }
1214
1215 struct passwd *pw = getpwuid(newuid);
1216 if (pw == nullptr) {
1217 if (setgroups(0, nullptr) < 0) {
1218 g_log<<Logger::Error<<"Unable to drop supplementary gids: "<<stringerror()<<endl;
1219 had_error = true;
1220 }
1221 } else {
1222 if (initgroups(pw->pw_name, newgid) < 0) {
1223 g_log<<Logger::Error<<"Unable to set supplementary groups: "<<stringerror()<<endl;
1224 had_error = true;
1225 }
1226 }
1227
1228 g_log<<Logger::Notice<<"Dropping effective user-id to "<<newuid<<endl;
1229 if (setuid(newuid) < 0) {
1230 g_log<<Logger::Error<<"Could not set user id to "<<newuid<<": "<<stringerror()<<endl;
1231 had_error = true;
1232 }
1233 }
1234
1235 if (had_error) {
1236 // We have already sent the errors to stderr, just die
1237 return EXIT_FAILURE;
1238 }
1239
1240 // It all starts here
1241 signal(SIGTERM, handleSignal);
1242 signal(SIGINT, handleSignal);
1243 signal(SIGPIPE, SIG_IGN);
1244
1245 // Init the things we need
1246 reportAllTypes();
1247
1248 dns_random_init();
1249
1250 std::thread ut(updateThread,
1251 config["work-dir"].as<string>(),
1252 config["keep"].as<uint16_t>(),
1253 config["axfr-timeout"].as<uint16_t>(),
1254 config["failed-soa-retry"].as<uint16_t>());
1255
1256 vector<std::thread> tcpHandlers;
1257 tcpHandlers.reserve(config["tcp-in-threads"].as<uint16_t>());
1258 for (size_t i = 0; i < tcpHandlers.capacity(); ++i) {
1259 tcpHandlers.push_back(std::thread(tcpWorker, i));
1260 }
1261
1262 struct timeval now;
1263 for(;;) {
1264 gettimeofday(&now, 0);
1265 fdm->run(&now);
1266 if (g_exiting) {
1267 g_log<<Logger::Debug<<"Closing listening sockets"<<endl;
1268 for (const int& fd : allSockets) {
1269 try {
1270 closesocket(fd);
1271 } catch(PDNSException &e) {
1272 g_log<<Logger::Error<<e.reason<<endl;
1273 }
1274 }
1275 break;
1276 }
1277 }
1278 g_log<<Logger::Debug<<"Waiting for all threads to stop"<<endl;
1279 g_tcpHandlerCV.notify_all();
1280 ut.join();
1281 for (auto &t : tcpHandlers) {
1282 t.join();
1283 }
1284 g_log<<Logger::Notice<<"IXFR distributor stopped"<<endl;
1285 return EXIT_SUCCESS;
1286 }