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