]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ixfrdist.cc
Merge pull request #6668 from zeha/api-zone-lookup
[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) {
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 unsigned int 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 axfr_now = time(nullptr);
352 if (axfr_now - t_start > axfrTimeout) {
353 throw PDNSException("Total AXFR time exceeded!");
354 }
355 }
356 if (soa == nullptr) {
357 g_log<<Logger::Warning<<"No SOA was found in the AXFR of "<<domain<<endl;
358 continue;
359 }
360 g_log<<Logger::Notice<<"Retrieved all zone data for "<<domain<<". Received "<<nrecords<<" records."<<endl;
361 } catch (PDNSException &e) {
362 g_log<<Logger::Warning<<"Could not retrieve AXFR for '"<<domain<<"': "<<e.reason<<endl;
363 continue;
364 } catch (runtime_error &e) {
365 g_log<<Logger::Warning<<"Could not retrieve AXFR for zone '"<<domain<<"': "<<e.what()<<endl;
366 continue;
367 }
368
369 try {
370
371 writeZoneToDisk(records, domain, dir);
372 g_log<<Logger::Notice<<"Wrote zonedata for "<<domain<<" with serial "<<soa->d_st.serial<<" to "<<dir<<endl;
373
374 const auto oldZoneInfo = getCurrentZoneInfo(domain);
375 auto zoneInfo = std::make_shared<ixfrinfo_t>();
376
377 if (oldZoneInfo && !oldZoneInfo->latestAXFR.empty()) {
378 auto diff = std::make_shared<ixfrdiff_t>();
379 zoneInfo->ixfrDiffs = oldZoneInfo->ixfrDiffs;
380 g_log<<Logger::Debug<<"Calculating diff for "<<domain<<endl;
381 makeIXFRDiff(oldZoneInfo->latestAXFR, records, diff, oldZoneInfo->soa, soa);
382 g_log<<Logger::Debug<<"Calculated diff for "<<domain<<", we had "<<diff->removals.size()<<" removals and "<<diff->additions.size()<<" additions"<<endl;
383 zoneInfo->ixfrDiffs.push_back(std::move(diff));
384 }
385
386 // Clean up the diffs
387 while (zoneInfo->ixfrDiffs.size() > keep) {
388 zoneInfo->ixfrDiffs.erase(zoneInfo->ixfrDiffs.begin());
389 }
390
391 g_log<<Logger::Debug<<"Zone "<<domain<<" previously contained "<<(oldZoneInfo ? oldZoneInfo->latestAXFR.size() : 0)<<" entries, "<<records.size()<<" now"<<endl;
392 zoneInfo->latestAXFR = std::move(records);
393 zoneInfo->soa = soa;
394 updateCurrentZoneInfo(domain, zoneInfo);
395 } catch (PDNSException &e) {
396 g_log<<Logger::Warning<<"Could not save zone '"<<domain<<"' to disk: "<<e.reason<<endl;
397 } catch (runtime_error &e) {
398 g_log<<Logger::Warning<<"Could not save zone '"<<domain<<"' to disk: "<<e.what()<<endl;
399 }
400
401 // Now clean up the directory
402 cleanUpDomain(domain, keep, workdir);
403 } /* for (const auto &domain : domains) */
404 sleep(1);
405 } /* while (true) */
406 } /* updateThread */
407
408 bool checkQuery(const MOADNSParser& mdp, const ComboAddress& saddr, const bool udp = true, const string& logPrefix="") {
409 vector<string> info_msg;
410
411 g_log<<Logger::Debug<<logPrefix<<"Had "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).getName()<<" query from "<<saddr.toStringWithPort()<<endl;
412
413 if (udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR) {
414 info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).getName() + " is not in {SOA,IXFR}");
415 }
416
417 if (!udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR && mdp.d_qtype != QType::AXFR) {
418 info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).getName() + " is not in {SOA,IXFR,AXFR}");
419 }
420
421 {
422 if (g_domainConfigs.find(mdp.d_qname) == g_domainConfigs.end()) {
423 info_msg.push_back("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for distribution");
424 }
425
426 const auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
427 if (zoneInfo == nullptr) {
428 info_msg.push_back("Domain has not been transferred yet");
429 }
430 }
431
432 if (!info_msg.empty()) {
433 g_log<<Logger::Warning<<logPrefix<<"Ignoring "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).getName()<<" query from "<<saddr.toStringWithPort();
434 g_log<<Logger::Warning<<": ";
435 bool first = true;
436 for (const auto& s : info_msg) {
437 if (!first) {
438 g_log<<Logger::Warning<<", ";
439 first = false;
440 }
441 g_log<<Logger::Warning<<s;
442 }
443 g_log<<Logger::Warning<<endl;
444 return false;
445 }
446
447 return true;
448 }
449
450 /*
451 * Returns a vector<uint8_t> that represents the full response to a SOA
452 * query. QNAME is read from mdp.
453 */
454 bool makeSOAPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
455
456 auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
457 if (zoneInfo == nullptr) {
458 return false;
459 }
460
461 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
462 pw.getHeader()->id = mdp.d_header.id;
463 pw.getHeader()->rd = mdp.d_header.rd;
464 pw.getHeader()->qr = 1;
465
466 pw.startRecord(mdp.d_qname, QType::SOA);
467 zoneInfo->soa->toPacket(pw);
468 pw.commit();
469
470 return true;
471 }
472
473 vector<uint8_t> getSOAPacket(const MOADNSParser& mdp, const shared_ptr<SOARecordContent>& soa) {
474 vector<uint8_t> packet;
475 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
476 pw.getHeader()->id = mdp.d_header.id;
477 pw.getHeader()->rd = mdp.d_header.rd;
478 pw.getHeader()->qr = 1;
479
480 // Add the first SOA
481 pw.startRecord(mdp.d_qname, QType::SOA);
482 soa->toPacket(pw);
483 pw.commit();
484 return packet;
485 }
486
487 bool makeAXFRPackets(const MOADNSParser& mdp, vector<vector<uint8_t>>& packets) {
488 /* we get a shared pointer of the zone info that we can't modify, ever.
489 A newer one may arise in the meantime, but this one will stay valid
490 until we release it.
491 */
492 auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
493 if (zoneInfo == nullptr) {
494 return false;
495 }
496
497 shared_ptr<SOARecordContent> soa = zoneInfo->soa;
498 const records_t& records = zoneInfo->latestAXFR;
499 packets.reserve(packets.size() + /* SOAs */ 2 + records.size());
500
501 // Initial SOA
502 const auto soaPacket = getSOAPacket(mdp, soa);
503 packets.push_back(soaPacket);
504
505 for (auto const &record : records) {
506 if (record.d_type == QType::SOA) {
507 continue;
508 }
509 vector<uint8_t> packet;
510 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
511 pw.getHeader()->id = mdp.d_header.id;
512 pw.getHeader()->rd = mdp.d_header.rd;
513 pw.getHeader()->qr = 1;
514 pw.startRecord(record.d_name + mdp.d_qname, record.d_type);
515 record.d_content->toPacket(pw);
516 pw.commit();
517 packets.push_back(packet);
518 }
519
520 // Final SOA
521 packets.push_back(soaPacket);
522
523 return true;
524 }
525
526 void makeXFRPacketsFromDNSRecords(const MOADNSParser& mdp, const vector<DNSRecord>& records, vector<vector<uint8_t>>& packets) {
527
528 for(const auto& r : records) {
529 if (r.d_type == QType::SOA) {
530 continue;
531 }
532
533 vector<uint8_t> packet;
534 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
535 pw.getHeader()->id = mdp.d_header.id;
536 pw.getHeader()->rd = mdp.d_header.rd;
537 pw.getHeader()->qr = 1;
538 pw.startRecord(r.d_name + mdp.d_qname, r.d_type);
539 r.d_content->toPacket(pw);
540 pw.commit();
541 packets.push_back(packet);
542 }
543 }
544
545 /* Produces an IXFR if one can be made according to the rules in RFC 1995 and
546 * creates a SOA or AXFR packet when required by the RFC.
547 */
548 bool makeIXFRPackets(const MOADNSParser& mdp, const shared_ptr<SOARecordContent>& clientSOA, vector<vector<uint8_t>>& packets) {
549 vector<std::shared_ptr<ixfrdiff_t>> toSend;
550
551 /* we get a shared pointer of the zone info that we can't modify, ever.
552 A newer one may arise in the meantime, but this one will stay valid
553 until we release it.
554 */
555 auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
556 if (zoneInfo == nullptr) {
557 return false;
558 }
559
560 uint32_t ourLatestSerial = zoneInfo->soa->d_st.serial;
561
562 if (rfc1982LessThan(ourLatestSerial, clientSOA->d_st.serial) || ourLatestSerial == clientSOA->d_st.serial) {
563 /* RFC 1995 Section 2
564 * If an IXFR query with the same or newer version number than that of
565 * the server is received, it is replied to with a single SOA record of
566 * the server's current version, just as in AXFR.
567 */
568 vector<uint8_t> packet;
569 bool ret = makeSOAPacket(mdp, packet);
570 if (ret) {
571 packets.push_back(packet);
572 }
573 return ret;
574 }
575
576 // as we use push_back in the updater, we know the vector is sorted as oldest first
577 bool shouldAdd = false;
578 // Get all relevant IXFR differences
579 for (const auto& diff : zoneInfo->ixfrDiffs) {
580 if (shouldAdd) {
581 toSend.push_back(diff);
582 continue;
583 }
584 if (diff->oldSOA->d_st.serial == clientSOA->d_st.serial) {
585 toSend.push_back(diff);
586 // Add all consecutive diffs
587 shouldAdd = true;
588 }
589 }
590
591 if (toSend.empty()) {
592 g_log<<Logger::Warning<<"No IXFR available from serial "<<clientSOA->d_st.serial<<" for zone "<<mdp.d_qname<<", attempting to send AXFR"<<endl;
593 return makeAXFRPackets(mdp, packets);
594 }
595
596 for (const auto& diff : toSend) {
597 /* An IXFR packet's ANSWER section looks as follows:
598 * SOA new_serial
599 * SOA old_serial
600 * ... removed records ...
601 * SOA new_serial
602 * ... added records ...
603 * SOA new_serial
604 */
605 packets.reserve(packets.size() + /* SOAs */ 4 + diff->removals.size() + diff->additions.size());
606
607 packets.push_back(getSOAPacket(mdp, diff->newSOA));
608 packets.push_back(getSOAPacket(mdp, diff->oldSOA));
609 makeXFRPacketsFromDNSRecords(mdp, diff->removals, packets);
610 packets.push_back(getSOAPacket(mdp, diff->newSOA));
611 makeXFRPacketsFromDNSRecords(mdp, diff->additions, packets);
612 packets.push_back(getSOAPacket(mdp, diff->newSOA));
613 }
614
615 return true;
616 }
617
618 bool allowedByACL(const ComboAddress& addr) {
619 return g_acl.match(addr);
620 }
621
622 void handleUDPRequest(int fd, boost::any&) {
623 // TODO make the buffer-size configurable
624 char buf[4096];
625 ComboAddress saddr;
626 socklen_t fromlen = sizeof(saddr);
627 int res = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*) &saddr, &fromlen);
628
629 if (res == 0) {
630 g_log<<Logger::Warning<<"Got an empty message from "<<saddr.toStringWithPort()<<endl;
631 return;
632 }
633
634 if(res < 0) {
635 auto savedErrno = errno;
636 g_log<<Logger::Warning<<"Could not read message from "<<saddr.toStringWithPort()<<": "<<strerror(savedErrno)<<endl;
637 return;
638 }
639
640 if (saddr == ComboAddress("0.0.0.0", 0)) {
641 g_log<<Logger::Warning<<"Could not determine source of message"<<endl;
642 return;
643 }
644
645 if (!allowedByACL(saddr)) {
646 g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
647 return;
648 }
649
650 MOADNSParser mdp(true, string(buf, res));
651 if (!checkQuery(mdp, saddr)) {
652 return;
653 }
654
655 /* RFC 1995 Section 2
656 * Transport of a query may be by either UDP or TCP. If an IXFR query
657 * is via UDP, the IXFR server may attempt to reply using UDP if the
658 * entire response can be contained in a single DNS packet. If the UDP
659 * reply does not fit, the query is responded to with a single SOA
660 * record of the server's current version to inform the client that a
661 * TCP query should be initiated.
662 *
663 * Let's not complicate this with IXFR over UDP (and looking if we need to truncate etc).
664 * Just send the current SOA and let the client try over TCP
665 */
666 vector<uint8_t> packet;
667 makeSOAPacket(mdp, packet);
668 if(sendto(fd, &packet[0], packet.size(), 0, (struct sockaddr*) &saddr, fromlen) < 0) {
669 auto savedErrno = errno;
670 g_log<<Logger::Warning<<"Could not send reply for "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).getName()<<" to "<<saddr.toStringWithPort()<<": "<<strerror(savedErrno)<<endl;
671 }
672 return;
673 }
674
675 void handleTCPRequest(int fd, boost::any&) {
676 ComboAddress saddr;
677 int cfd = 0;
678
679 try {
680 cfd = SAccept(fd, saddr);
681 setBlocking(cfd);
682 } catch(runtime_error &e) {
683 g_log<<Logger::Error<<e.what()<<endl;
684 return;
685 }
686
687 if (saddr == ComboAddress("0.0.0.0", 0)) {
688 g_log<<Logger::Warning<<"Could not determine source of message"<<endl;
689 close(cfd);
690 return;
691 }
692
693 if (!allowedByACL(saddr)) {
694 g_log<<Logger::Warning<<"TCP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
695 close(cfd);
696 return;
697 }
698
699 {
700 std::lock_guard<std::mutex> lg(g_tcpRequestFDsMutex);
701 g_tcpRequestFDs.push({cfd, saddr});
702 }
703 g_tcpHandlerCV.notify_one();
704 }
705
706 /* Thread to handle TCP traffic
707 */
708 void tcpWorker(int tid) {
709 string prefix = "TCP Worker " + std::to_string(tid) + ": ";
710
711 while(true) {
712 g_log<<Logger::Debug<<prefix<<"ready for a new request!"<<endl;
713 std::unique_lock<std::mutex> lk(g_tcpRequestFDsMutex);
714 g_tcpHandlerCV.wait(lk, []{return g_tcpRequestFDs.size() || g_exiting ;});
715 if (g_exiting) {
716 g_log<<Logger::Debug<<prefix<<"Stopping thread"<<endl;
717 break;
718 }
719 g_log<<Logger::Debug<<prefix<<"Going to handle a query"<<endl;
720 auto request = g_tcpRequestFDs.front();
721 g_tcpRequestFDs.pop();
722 lk.unlock();
723
724 int cfd = request.first;
725 ComboAddress saddr = request.second;
726
727 char buf[4096];
728 ssize_t res;
729 try {
730 uint16_t toRead;
731 readn2(cfd, &toRead, sizeof(toRead));
732 toRead = std::min(ntohs(toRead), static_cast<uint16_t>(sizeof(buf)));
733 res = readn2WithTimeout(cfd, &buf, toRead, 2);
734 g_log<<Logger::Debug<<prefix<<"Had message of "<<std::to_string(toRead)<<" bytes from "<<saddr.toStringWithPort()<<endl;
735 } catch (runtime_error &e) {
736 g_log<<Logger::Warning<<prefix<<"Could not read message from "<<saddr.toStringWithPort()<<": "<<e.what()<<endl;
737 close(cfd);
738 continue;
739 }
740
741 try {
742 MOADNSParser mdp(true, string(buf, res));
743
744 if (!checkQuery(mdp, saddr, false, prefix)) {
745 close(cfd);
746 continue;
747 }
748
749 vector<vector<uint8_t>> packets;
750 if (mdp.d_qtype == QType::SOA) {
751 vector<uint8_t> packet;
752 bool ret = makeSOAPacket(mdp, packet);
753 if (!ret) {
754 close(cfd);
755 continue;
756 }
757 packets.push_back(packet);
758 }
759
760 if (mdp.d_qtype == QType::AXFR) {
761 if (!makeAXFRPackets(mdp, packets)) {
762 close(cfd);
763 continue;
764 }
765 }
766
767 if (mdp.d_qtype == QType::IXFR) {
768 /* RFC 1995 section 3:
769 * The IXFR query packet format is the same as that of a normal DNS
770 * query, but with the query type being IXFR and the authority section
771 * containing the SOA record of client's version of the zone.
772 */
773 shared_ptr<SOARecordContent> clientSOA;
774 for (auto &answer : mdp.d_answers) {
775 // from dnsparser.hh:
776 // typedef vector<pair<DNSRecord, uint16_t > > answers_t;
777 if (answer.first.d_type == QType::SOA && answer.first.d_place == DNSResourceRecord::AUTHORITY) {
778 clientSOA = getRR<SOARecordContent>(answer.first);
779 if (clientSOA != nullptr) {
780 break;
781 }
782 }
783 } /* for (auto const &answer : mdp.d_answers) */
784
785 if (clientSOA == nullptr) {
786 g_log<<Logger::Warning<<prefix<<"IXFR request packet did not contain a SOA record in the AUTHORITY section"<<endl;
787 close(cfd);
788 continue;
789 }
790
791 if (!makeIXFRPackets(mdp, clientSOA, packets)) {
792 close(cfd);
793 continue;
794 }
795 } /* if (mdp.d_qtype == QType::IXFR) */
796
797 g_log<<Logger::Debug<<prefix<<"Sending "<<packets.size()<<" packets to "<<saddr.toStringWithPort()<<endl;
798 for (const auto& packet : packets) {
799 char sendBuf[2];
800 sendBuf[0]=packet.size()/256;
801 sendBuf[1]=packet.size()%256;
802
803 ssize_t send = writen2(cfd, sendBuf, 2);
804 send += writen2(cfd, &packet[0], packet.size());
805 }
806 shutdown(cfd, 2);
807 } catch (MOADNSException &e) {
808 g_log<<Logger::Warning<<prefix<<"Could not parse DNS packet from "<<saddr.toStringWithPort()<<": "<<e.what()<<endl;
809 } catch (runtime_error &e) {
810 g_log<<Logger::Warning<<prefix<<"Could not write reply to "<<saddr.toStringWithPort()<<": "<<e.what()<<endl;
811 }
812 // bye!
813 close(cfd);
814
815 if (g_exiting) {
816 break;
817 }
818 }
819 }
820
821 /* Parses the configuration file in configpath into config, adding defaults for
822 * missing parameters (if applicable), returning true if the config file was
823 * good, false otherwise. Will log all issues with the config
824 */
825 bool parseAndCheckConfig(const string& configpath, YAML::Node& config) {
826 g_log<<Logger::Info<<"Loading configuration file from "<<configpath<<endl;
827 try {
828 config = YAML::LoadFile(configpath);
829 } catch (const runtime_error &e) {
830 g_log<<Logger::Error<<"Unable to load configuration file '"<<configpath<<"': "<<e.what()<<endl;
831 return false;
832 }
833
834 bool retval = true;
835
836 if (config["keep"]) {
837 try {
838 config["keep"].as<uint16_t>();
839 } catch (const runtime_error &e) {
840 g_log<<Logger::Error<<"Unable to read 'keep' value: "<<e.what()<<endl;
841 retval = false;
842 }
843 } else {
844 config["keep"] = 20;
845 }
846
847 if (config["axfr-timeout"]) {
848 try {
849 config["axfr-timeout"].as<uint16_t>();
850 } catch (const runtime_error &e) {
851 g_log<<Logger::Error<<"Unable to read 'axfr-timeout' value: "<<e.what()<<endl;
852 }
853 } else {
854 config["axfr-timeout"] = 20;
855 }
856
857 if (config["tcp-in-threads"]) {
858 try {
859 config["tcp-in-threads"].as<uint16_t>();
860 } catch (const runtime_error &e) {
861 g_log<<Logger::Error<<"Unable to read 'tcp-in-threads' value: "<<e.what()<<endl;
862 }
863 } else {
864 config["tcp-in-threads"] = 10;
865 }
866
867 if (config["listen"]) {
868 try {
869 config["listen"].as<vector<ComboAddress>>();
870 } catch (const runtime_error &e) {
871 g_log<<Logger::Error<<"Unable to read 'listen' value: "<<e.what()<<endl;
872 retval = false;
873 }
874 } else {
875 config["listen"].push_back("127.0.0.1:53");
876 config["listen"].push_back("[::1]:53");
877 }
878
879 if (config["acl"]) {
880 try {
881 config["acl"].as<vector<string>>();
882 } catch (const runtime_error &e) {
883 g_log<<Logger::Error<<"Unable to read 'acl' value: "<<e.what()<<endl;
884 retval = false;
885 }
886 } else {
887 config["acl"].push_back("127.0.0.0/8");
888 config["acl"].push_back("::1/128");
889 }
890
891 if (config["work-dir"]) {
892 try {
893 config["work-dir"].as<string>();
894 } catch(const runtime_error &e) {
895 g_log<<Logger::Error<<"Unable to read 'work-dir' value: "<<e.what()<<endl;
896 retval = false;
897 }
898 } else {
899 char tmp[512];
900 config["work-dir"] = getcwd(tmp, sizeof(tmp)) ? string(tmp) : "";;
901 }
902
903 if (config["uid"]) {
904 try {
905 config["uid"].as<string>();
906 } catch(const runtime_error &e) {
907 g_log<<Logger::Error<<"Unable to read 'uid' value: "<<e.what()<<endl;
908 retval = false;
909 }
910 }
911
912 if (config["gid"]) {
913 try {
914 config["gid"].as<string>();
915 } catch(const runtime_error &e) {
916 g_log<<Logger::Error<<"Unable to read 'gid' value: "<<e.what()<<endl;
917 retval = false;
918 }
919 }
920
921 if (config["domains"]) {
922 if (config["domains"].size() == 0) {
923 g_log<<Logger::Error<<"No domains configured"<<endl;
924 retval = false;
925 }
926 for (auto const &domain : config["domains"]) {
927 try {
928 if (!domain["domain"]) {
929 g_log<<Logger::Error<<"An entry in 'domains' is missing a 'domain' key!"<<endl;
930 retval = false;
931 continue;
932 }
933 domain["domain"].as<DNSName>();
934 } catch (const runtime_error &e) {
935 g_log<<Logger::Error<<"Unable to read domain '"<<domain["domain"].as<string>()<<"': "<<e.what()<<endl;
936 }
937 try {
938 if (!domain["master"]) {
939 g_log<<Logger::Error<<"Domain '"<<domain["domain"].as<string>()<<"' has no master configured!"<<endl;
940 retval = false;
941 continue;
942 }
943 domain["master"].as<ComboAddress>();
944 } catch (const runtime_error &e) {
945 g_log<<Logger::Error<<"Unable to read domain '"<<domain["domain"].as<string>()<<"' master address: "<<e.what()<<endl;
946 retval = false;
947 }
948 }
949 } else {
950 g_log<<Logger::Error<<"No domains configured"<<endl;
951 retval = false;
952 }
953
954 return retval;
955 }
956
957 int main(int argc, char** argv) {
958 g_log.setLoglevel(Logger::Notice);
959 g_log.toConsole(Logger::Notice);
960 g_log.setPrefixed(true);
961 g_log.disableSyslog(true);
962 g_log.setTimestamps(false);
963 po::variables_map g_vm;
964 try {
965 po::options_description desc("IXFR distribution tool");
966 desc.add_options()
967 ("help", "produce help message")
968 ("version", "Display the version of ixfrdist")
969 ("verbose", "Be verbose")
970 ("debug", "Be even more verbose")
971 ("config", po::value<string>()->default_value(SYSCONFDIR + string("/ixfrdist.yml")), "Configuration file to use")
972 ;
973
974 po::store(po::command_line_parser(argc, argv).options(desc).run(), g_vm);
975 po::notify(g_vm);
976
977 if (g_vm.count("help") > 0) {
978 usage(desc);
979 return EXIT_SUCCESS;
980 }
981
982 if (g_vm.count("version") > 0) {
983 cout<<"ixfrdist "<<VERSION<<endl;
984 return EXIT_SUCCESS;
985 }
986 } catch (po::error &e) {
987 g_log<<Logger::Error<<e.what()<<". See `ixfrdist --help` for valid options"<<endl;
988 return(EXIT_FAILURE);
989 }
990
991 bool had_error = false;
992
993 if (g_vm.count("verbose")) {
994 g_log.setLoglevel(Logger::Info);
995 g_log.toConsole(Logger::Info);
996 }
997
998 if (g_vm.count("debug") > 0) {
999 g_log.setLoglevel(Logger::Debug);
1000 g_log.toConsole(Logger::Debug);
1001 }
1002
1003 g_log<<Logger::Notice<<"IXFR distributor version "<<VERSION<<" starting up!"<<endl;
1004
1005 YAML::Node config;
1006 if (!parseAndCheckConfig(g_vm["config"].as<string>(), config)) {
1007 // parseAndCheckConfig already logged whatever was wrong
1008 return EXIT_FAILURE;
1009 }
1010
1011 /* From hereon out, we known that all the values in config are valid. */
1012
1013 for (auto const &domain : config["domains"]) {
1014 set<ComboAddress> s;
1015 s.insert(domain["master"].as<ComboAddress>());
1016 g_domainConfigs[domain["domain"].as<DNSName>()].masters = s;
1017 }
1018
1019 for (const auto &addr : config["acl"].as<vector<string>>()) {
1020 try {
1021 g_acl.addMask(addr);
1022 } catch (const NetmaskException &e) {
1023 g_log<<Logger::Error<<e.reason<<endl;
1024 had_error = true;
1025 }
1026 }
1027 g_log<<Logger::Notice<<"ACL set to "<<g_acl.toString()<<"."<<endl;
1028
1029 FDMultiplexer* fdm = FDMultiplexer::getMultiplexerSilent();
1030 if (fdm == nullptr) {
1031 g_log<<Logger::Error<<"Could not enable a multiplexer for the listen sockets!"<<endl;
1032 return EXIT_FAILURE;
1033 }
1034
1035 set<int> allSockets;
1036 for (const auto& addr : config["listen"].as<vector<ComboAddress>>()) {
1037 for (const auto& stype : {SOCK_DGRAM, SOCK_STREAM}) {
1038 try {
1039 int s = SSocket(addr.sin4.sin_family, stype, 0);
1040 setNonBlocking(s);
1041 setReuseAddr(s);
1042 SBind(s, addr);
1043 if (stype == SOCK_STREAM) {
1044 SListen(s, 30); // TODO make this configurable
1045 }
1046 fdm->addReadFD(s, stype == SOCK_DGRAM ? handleUDPRequest : handleTCPRequest);
1047 allSockets.insert(s);
1048 } catch(runtime_error &e) {
1049 g_log<<Logger::Error<<e.what()<<endl;
1050 had_error = true;
1051 continue;
1052 }
1053 }
1054 }
1055
1056 int newgid = 0;
1057
1058 if (config["gid"]) {
1059 string gid = config["gid"].as<string>();
1060 if (!(newgid = atoi(gid.c_str()))) {
1061 struct group *gr = getgrnam(gid.c_str());
1062 if (gr == nullptr) {
1063 g_log<<Logger::Error<<"Can not determine group-id for gid "<<gid<<endl;
1064 had_error = true;
1065 } else {
1066 newgid = gr->gr_gid;
1067 }
1068 }
1069 g_log<<Logger::Notice<<"Dropping effective group-id to "<<newgid<<endl;
1070 if (setgid(newgid) < 0) {
1071 g_log<<Logger::Error<<"Could not set group id to "<<newgid<<": "<<stringerror()<<endl;
1072 had_error = true;
1073 }
1074 }
1075
1076 int newuid = 0;
1077
1078 if (config["uid"]) {
1079 string uid = config["uid"].as<string>();
1080 if (!(newuid = atoi(uid.c_str()))) {
1081 struct passwd *pw = getpwnam(uid.c_str());
1082 if (pw == nullptr) {
1083 g_log<<Logger::Error<<"Can not determine user-id for uid "<<uid<<endl;
1084 had_error = true;
1085 } else {
1086 newuid = pw->pw_uid;
1087 }
1088 }
1089
1090 struct passwd *pw = getpwuid(newuid);
1091 if (pw == nullptr) {
1092 if (setgroups(0, nullptr) < 0) {
1093 g_log<<Logger::Error<<"Unable to drop supplementary gids: "<<stringerror()<<endl;
1094 had_error = true;
1095 }
1096 } else {
1097 if (initgroups(pw->pw_name, newgid) < 0) {
1098 g_log<<Logger::Error<<"Unable to set supplementary groups: "<<stringerror()<<endl;
1099 had_error = true;
1100 }
1101 }
1102
1103 g_log<<Logger::Notice<<"Dropping effective user-id to "<<newuid<<endl;
1104 if (setuid(newuid) < 0) {
1105 g_log<<Logger::Error<<"Could not set user id to "<<newuid<<": "<<stringerror()<<endl;
1106 had_error = true;
1107 }
1108 }
1109
1110 if (had_error) {
1111 // We have already sent the errors to stderr, just die
1112 return EXIT_FAILURE;
1113 }
1114
1115 // It all starts here
1116 signal(SIGTERM, handleSignal);
1117 signal(SIGINT, handleSignal);
1118 signal(SIGPIPE, SIG_IGN);
1119
1120 // Init the things we need
1121 reportAllTypes();
1122
1123 dns_random_init();
1124
1125 std::thread ut(updateThread,
1126 config["work-dir"].as<string>(),
1127 config["keep"].as<uint16_t>(),
1128 config["axfr-timeout"].as<uint16_t>());
1129
1130 vector<std::thread> tcpHandlers;
1131 tcpHandlers.reserve(config["tcp-in-threads"].as<uint16_t>());
1132 for (size_t i = 0; i < tcpHandlers.capacity(); ++i) {
1133 tcpHandlers.push_back(std::thread(tcpWorker, i));
1134 }
1135
1136 struct timeval now;
1137 for(;;) {
1138 gettimeofday(&now, 0);
1139 fdm->run(&now);
1140 if (g_exiting) {
1141 g_log<<Logger::Debug<<"Closing listening sockets"<<endl;
1142 for (const int& fd : allSockets) {
1143 try {
1144 closesocket(fd);
1145 } catch(PDNSException &e) {
1146 g_log<<Logger::Error<<e.reason<<endl;
1147 }
1148 }
1149 break;
1150 }
1151 }
1152 g_log<<Logger::Debug<<"Waiting for al threads to stop"<<endl;
1153 g_tcpHandlerCV.notify_all();
1154 ut.join();
1155 for (auto &t : tcpHandlers) {
1156 t.join();
1157 }
1158 g_log<<Logger::Notice<<"IXFR distributor stopped"<<endl;
1159 return EXIT_SUCCESS;
1160 }