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