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