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