2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
25 #include <boost/program_options.hpp>
26 #include <arpa/inet.h>
27 #include <sys/types.h>
35 #include <condition_variable>
37 #include "ixfrutils.hh"
38 #include "resolver.hh"
39 #include "dns_random.hh"
45 #include <yaml-cpp/yaml.h>
47 /* BEGIN Needed because of deeper dependencies */
48 #include "arguments.hh"
54 static ArgvMap theArg
;
57 /* END Needed because of deeper dependencies */
59 // Allows reading/writing ComboAddresses and DNSNames in YAML-cpp
62 struct convert
<ComboAddress
> {
63 static Node
encode(const ComboAddress
& rhs
) {
64 return Node(rhs
.toStringWithPort());
66 static bool decode(const Node
& node
, ComboAddress
& rhs
) {
67 if (!node
.IsScalar()) {
71 rhs
= ComboAddress(node
.as
<string
>(), 53);
73 } catch(const runtime_error
&e
) {
75 } catch (const PDNSException
&e
) {
82 struct convert
<DNSName
> {
83 static Node
encode(const DNSName
& rhs
) {
84 return Node(rhs
.toStringRootDot());
86 static bool decode(const Node
& node
, DNSName
& rhs
) {
87 if (!node
.IsScalar()) {
91 rhs
= DNSName(node
.as
<string
>());
93 } catch(const runtime_error
&e
) {
95 } catch (const PDNSException
&e
) {
102 // Why a struct? This way we can add more options to a domain in the future
103 struct ixfrdistdomain_t
{
104 set
<ComboAddress
> masters
; // A set so we can do multiple master addresses in the future
107 // This contains the configuration for each domain
108 map
<DNSName
, ixfrdistdomain_t
> g_domainConfigs
;
110 // Map domains and their data
111 std::map
<DNSName
, ixfrinfo_t
> g_soas
;
112 std::mutex g_soas_mutex
;
114 // Condition variable for TCP handling
115 std::condition_variable g_tcpHandlerCV
;
116 std::queue
<pair
<int, ComboAddress
>> g_tcpRequestFDs
;
117 std::mutex g_tcpRequestFDsMutex
;
119 namespace po
= boost::program_options
;
121 bool g_exiting
= false;
125 void handleSignal(int signum
) {
126 g_log
<<Logger::Notice
<<"Got "<<strsignal(signum
)<<" signal";
128 g_log
<<Logger::Notice
<<", this is the second time we were asked to stop, forcefully exiting"<<endl
;
131 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
;
135 void usage(po::options_description
&desc
) {
136 cerr
<< "Usage: ixfrdist [OPTION]..."<<endl
;
137 cerr
<< desc
<< "\n";
140 // The compiler does not like using rfc1982LessThan in std::sort directly
141 bool sortSOA(uint32_t i
, uint32_t j
) {
142 return rfc1982LessThan(i
, j
);
145 void cleanUpDomain(const DNSName
& domain
, const uint16_t& keep
, const string
& workdir
) {
146 string dir
= workdir
+ "/" + domain
.toString();
148 dp
= opendir(dir
.c_str());
152 vector
<uint32_t> zoneVersions
;
154 while ((d
= readdir(dp
)) != nullptr) {
155 if(!strcmp(d
->d_name
, ".") || !strcmp(d
->d_name
, "..")) {
158 zoneVersions
.push_back(std::stoi(d
->d_name
));
161 g_log
<<Logger::Info
<<"Found "<<zoneVersions
.size()<<" versions of "<<domain
<<", asked to keep "<<keep
<<", ";
162 if (zoneVersions
.size() <= keep
) {
163 g_log
<<Logger::Info
<<"not cleaning up"<<endl
;
166 g_log
<<Logger::Info
<<"cleaning up the oldest "<<zoneVersions
.size() - keep
<<endl
;
169 std::sort(zoneVersions
.begin(), zoneVersions
.end(), sortSOA
);
171 // And delete all the old ones
173 // Lock to ensure no one reads this.
174 std::lock_guard
<std::mutex
> guard(g_soas_mutex
);
175 for (auto iter
= zoneVersions
.cbegin(); iter
!= zoneVersions
.cend() - keep
; ++iter
) {
176 string fname
= dir
+ "/" + std::to_string(*iter
);
177 g_log
<<Logger::Debug
<<"Removing "<<fname
<<endl
;
178 unlink(fname
.c_str());
183 static shared_ptr
<SOARecordContent
> getSOAFromRecords(const records_t
& records
) {
184 for (const auto& dnsrecord
: records
) {
185 if (dnsrecord
.d_type
== QType::SOA
) {
186 auto soa
= getRR
<SOARecordContent
>(dnsrecord
);
187 if (soa
== nullptr) {
188 throw PDNSException("Unable to determine SOARecordContent from old records");
193 throw PDNSException("No SOA in supplied records");
196 static void makeIXFRDiff(const records_t
& from
, const records_t
& to
, ixfrdiff_t
& diff
, const shared_ptr
<SOARecordContent
>& fromSOA
= nullptr, const shared_ptr
<SOARecordContent
>& toSOA
= nullptr) {
197 set_difference(from
.cbegin(), from
.cend(), to
.cbegin(), to
.cend(), back_inserter(diff
.removals
), from
.value_comp());
198 set_difference(to
.cbegin(), to
.cend(), from
.cbegin(), from
.cend(), back_inserter(diff
.additions
), from
.value_comp());
199 diff
.oldSOA
= fromSOA
;
200 if (fromSOA
== nullptr) {
201 getSOAFromRecords(from
);
204 if (toSOA
== nullptr) {
205 getSOAFromRecords(to
);
209 void updateThread(const string
& workdir
, const uint16_t& keep
, const uint16_t& axfrTimeout
) {
210 std::map
<DNSName
, time_t> lastCheck
;
212 // Initialize the serials we have
213 for (const auto &domainConfig
: g_domainConfigs
) {
214 DNSName domain
= domainConfig
.first
;
215 lastCheck
[domain
] = 0;
216 string dir
= workdir
+ "/" + domain
.toString();
218 g_log
<<Logger::Info
<<"Trying to initially load domain "<<domain
<<" from disk"<<endl
;
219 auto serial
= getSerialsFromDir(dir
);
220 shared_ptr
<SOARecordContent
> soa
;
222 string fname
= workdir
+ "/" + domain
.toString() + "/" + std::to_string(serial
);
223 loadSOAFromDisk(domain
, fname
, soa
);
225 if (soa
!= nullptr) {
226 loadZoneFromDisk(records
, fname
, domain
);
228 std::lock_guard
<std::mutex
> guard(g_soas_mutex
);
229 g_soas
[domain
].latestAXFR
= records
;
230 g_soas
[domain
].soa
= soa
;
232 if (soa
!= nullptr) {
233 g_log
<<Logger::Notice
<<"Loaded zone "<<domain
<<" with serial "<<soa
->d_st
.serial
<<endl
;
235 cleanUpDomain(domain
, keep
, workdir
);
237 } catch (runtime_error
&e
) {
238 // Most likely, the directory does not exist.
239 g_log
<<Logger::Info
<<e
.what()<<", attempting to create"<<endl
;
240 // Attempt to create it, if _that_ fails, there is no hope
241 if (mkdir(dir
.c_str(), 0777) == -1 && errno
!= EEXIST
) {
242 g_log
<<Logger::Error
<<"Could not create '"<<dir
<<"': "<<strerror(errno
)<<endl
;
248 g_log
<<Logger::Notice
<<"Update Thread started"<<endl
;
252 g_log
<<Logger::Notice
<<"UpdateThread stopped"<<endl
;
255 time_t now
= time(nullptr);
256 for (const auto &domainConfig
: g_domainConfigs
) {
257 DNSName domain
= domainConfig
.first
;
258 shared_ptr
<SOARecordContent
> current_soa
;
260 std::lock_guard
<std::mutex
> guard(g_soas_mutex
);
261 if (g_soas
.find(domain
) != g_soas
.end()) {
262 current_soa
= g_soas
[domain
].soa
;
265 if ((current_soa
!= nullptr && now
- lastCheck
[domain
] < current_soa
->d_st
.refresh
) || // Only check if we have waited `refresh` seconds
266 (current_soa
== nullptr && now
- lastCheck
[domain
] < 30)) { // Or if we could not get an update at all still, every 30 seconds
270 // TODO Keep track of 'down' masters
271 set
<ComboAddress
>::const_iterator
it(g_domainConfigs
[domain
].masters
.begin());
272 std::advance(it
, random() % g_domainConfigs
[domain
].masters
.size());
273 ComboAddress master
= *it
;
275 string dir
= workdir
+ "/" + domain
.toString();
276 g_log
<<Logger::Info
<<"Attempting to retrieve SOA Serial update for '"<<domain
<<"' from '"<<master
.toStringWithPort()<<"'"<<endl
;
277 shared_ptr
<SOARecordContent
> sr
;
279 lastCheck
[domain
] = now
;
280 auto newSerial
= getSerialFromMaster(master
, domain
, sr
); // TODO TSIG
281 if(current_soa
!= nullptr) {
282 g_log
<<Logger::Info
<<"Got SOA Serial for "<<domain
<<" from "<<master
.toStringWithPort()<<": "<< newSerial
<<", had Serial: "<<current_soa
->d_st
.serial
;
283 if (newSerial
== current_soa
->d_st
.serial
) {
284 g_log
<<Logger::Info
<<", not updating."<<endl
;
287 g_log
<<Logger::Info
<<", will update."<<endl
;
289 } catch (runtime_error
&e
) {
290 g_log
<<Logger::Warning
<<"Unable to get SOA serial update for '"<<domain
<<"' from master "<<master
.toStringWithPort()<<": "<<e
.what()<<endl
;
293 // Now get the full zone!
294 g_log
<<Logger::Info
<<"Attempting to receive full zonedata for '"<<domain
<<"'"<<endl
;
295 ComboAddress local
= master
.isIPv4() ? ComboAddress("0.0.0.0") : ComboAddress("::");
299 shared_ptr
<SOARecordContent
> soa
;
301 AXFRRetriever
axfr(master
, domain
, tt
, &local
);
302 unsigned int nrecords
=0;
304 vector
<DNSRecord
> chunk
;
306 time_t t_start
= time(nullptr);
307 time_t axfr_now
= time(nullptr);
308 while(axfr
.getChunk(nop
, &chunk
, (axfr_now
- t_start
+ axfrTimeout
))) {
309 for(auto& dr
: chunk
) {
310 if(dr
.d_type
== QType::TSIG
)
312 dr
.d_name
.makeUsRelative(domain
);
315 if (dr
.d_type
== QType::SOA
) {
316 soa
= getRR
<SOARecordContent
>(dr
);
319 axfr_now
= time(nullptr);
320 if (axfr_now
- t_start
> axfrTimeout
) {
321 throw PDNSException("Total AXFR time exceeded!");
324 if (soa
== nullptr) {
325 g_log
<<Logger::Warning
<<"No SOA was found in the AXFR of "<<domain
<<endl
;
328 g_log
<<Logger::Notice
<<"Retrieved all zone data for "<<domain
<<". Received "<<nrecords
<<" records."<<endl
;
329 writeZoneToDisk(records
, domain
, dir
);
330 g_log
<<Logger::Notice
<<"Wrote zonedata for "<<domain
<<" with serial "<<soa
->d_st
.serial
<<" to "<<dir
<<endl
;
332 std::lock_guard
<std::mutex
> guard(g_soas_mutex
);
334 if (!g_soas
[domain
].latestAXFR
.empty()) {
335 makeIXFRDiff(g_soas
[domain
].latestAXFR
, records
, diff
, g_soas
[domain
].soa
, soa
);
336 g_soas
[domain
].ixfrDiffs
.push_back(diff
);
338 // Clean up the diffs
339 while (g_soas
[domain
].ixfrDiffs
.size() > keep
) {
340 g_soas
[domain
].ixfrDiffs
.erase(g_soas
[domain
].ixfrDiffs
.begin());
342 g_soas
[domain
].latestAXFR
= records
;
343 g_soas
[domain
].soa
= soa
;
345 } catch (PDNSException
&e
) {
346 g_log
<<Logger::Warning
<<"Could not retrieve AXFR for '"<<domain
<<"': "<<e
.reason
<<endl
;
347 } catch (runtime_error
&e
) {
348 g_log
<<Logger::Warning
<<"Could not save zone '"<<domain
<<"' to disk: "<<e
.what()<<endl
;
350 // Now clean up the directory
351 cleanUpDomain(domain
, keep
, workdir
);
352 } /* for (const auto &domain : domains) */
357 bool checkQuery(const MOADNSParser
& mdp
, const ComboAddress
& saddr
, const bool udp
= true, const string
& logPrefix
="") {
358 vector
<string
> info_msg
;
360 g_log
<<Logger::Debug
<<logPrefix
<<"Had "<<mdp
.d_qname
<<"|"<<QType(mdp
.d_qtype
).getName()<<" query from "<<saddr
.toStringWithPort()<<endl
;
362 if (udp
&& mdp
.d_qtype
!= QType::SOA
&& mdp
.d_qtype
!= QType::IXFR
) {
363 info_msg
.push_back("QType is unsupported (" + QType(mdp
.d_qtype
).getName() + " is not in {SOA,IXFR}");
366 if (!udp
&& mdp
.d_qtype
!= QType::SOA
&& mdp
.d_qtype
!= QType::IXFR
&& mdp
.d_qtype
!= QType::AXFR
) {
367 info_msg
.push_back("QType is unsupported (" + QType(mdp
.d_qtype
).getName() + " is not in {SOA,IXFR,AXFR}");
371 std::lock_guard
<std::mutex
> guard(g_soas_mutex
);
372 if (g_domainConfigs
.find(mdp
.d_qname
) == g_domainConfigs
.end()) {
373 info_msg
.push_back("Domain name '" + mdp
.d_qname
.toLogString() + "' is not configured for distribution");
376 if (g_soas
.find(mdp
.d_qname
) == g_soas
.end()) {
377 info_msg
.push_back("Domain has not been transferred yet");
381 if (!info_msg
.empty()) {
382 g_log
<<Logger::Warning
<<logPrefix
<<"Ignoring "<<mdp
.d_qname
<<"|"<<QType(mdp
.d_qtype
).getName()<<" query from "<<saddr
.toStringWithPort();
383 g_log
<<Logger::Warning
<<": ";
385 for (const auto& s
: info_msg
) {
387 g_log
<<Logger::Warning
<<", ";
390 g_log
<<Logger::Warning
<<s
;
392 g_log
<<Logger::Warning
<<endl
;
400 * Returns a vector<uint8_t> that represents the full response to a SOA
401 * query. QNAME is read from mdp.
403 bool makeSOAPacket(const MOADNSParser
& mdp
, vector
<uint8_t>& packet
) {
404 DNSPacketWriter
pw(packet
, mdp
.d_qname
, mdp
.d_qtype
);
405 pw
.getHeader()->id
= mdp
.d_header
.id
;
406 pw
.getHeader()->rd
= mdp
.d_header
.rd
;
407 pw
.getHeader()->qr
= 1;
409 pw
.startRecord(mdp
.d_qname
, QType::SOA
);
411 std::lock_guard
<std::mutex
> guard(g_soas_mutex
);
412 g_soas
[mdp
.d_qname
].soa
->toPacket(pw
);
419 vector
<uint8_t> getSOAPacket(const MOADNSParser
& mdp
, const shared_ptr
<SOARecordContent
>& soa
) {
420 vector
<uint8_t> packet
;
421 DNSPacketWriter
pw(packet
, mdp
.d_qname
, mdp
.d_qtype
);
422 pw
.getHeader()->id
= mdp
.d_header
.id
;
423 pw
.getHeader()->rd
= mdp
.d_header
.rd
;
424 pw
.getHeader()->qr
= 1;
427 pw
.startRecord(mdp
.d_qname
, QType::SOA
);
433 bool makeAXFRPackets(const MOADNSParser
& mdp
, vector
<vector
<uint8_t>>& packets
) {
434 shared_ptr
<SOARecordContent
> soa
;
437 // Make copies of what we have
438 std::lock_guard
<std::mutex
> guard(g_soas_mutex
);
439 soa
= g_soas
[mdp
.d_qname
].soa
;
440 records
= g_soas
[mdp
.d_qname
].latestAXFR
;
444 packets
.push_back(getSOAPacket(mdp
, soa
));
446 for (auto const &record
: records
) {
447 if (record
.d_type
== QType::SOA
) {
450 vector
<uint8_t> packet
;
451 DNSPacketWriter
pw(packet
, mdp
.d_qname
, mdp
.d_qtype
);
452 pw
.getHeader()->id
= mdp
.d_header
.id
;
453 pw
.getHeader()->rd
= mdp
.d_header
.rd
;
454 pw
.getHeader()->qr
= 1;
455 pw
.startRecord(record
.d_name
+ mdp
.d_qname
, record
.d_type
);
456 record
.d_content
->toPacket(pw
);
458 packets
.push_back(packet
);
462 packets
.push_back(getSOAPacket(mdp
, soa
));
467 void makeXFRPacketsFromDNSRecords(const MOADNSParser
& mdp
, const vector
<DNSRecord
>& records
, vector
<vector
<uint8_t>>& packets
) {
468 for(const auto& r
: records
) {
469 if (r
.d_type
== QType::SOA
) {
472 vector
<uint8_t> packet
;
473 DNSPacketWriter
pw(packet
, mdp
.d_qname
, mdp
.d_qtype
);
474 pw
.getHeader()->id
= mdp
.d_header
.id
;
475 pw
.getHeader()->rd
= mdp
.d_header
.rd
;
476 pw
.getHeader()->qr
= 1;
477 pw
.startRecord(r
.d_name
+ mdp
.d_qname
, r
.d_type
);
478 r
.d_content
->toPacket(pw
);
480 packets
.push_back(packet
);
484 /* Produces an IXFR if one can be made according to the rules in RFC 1995 and
485 * creates a SOA or AXFR packet when required by the RFC.
487 bool makeIXFRPackets(const MOADNSParser
& mdp
, const shared_ptr
<SOARecordContent
>& clientSOA
, vector
<vector
<uint8_t>>& packets
) {
488 // Get the new SOA only once, so it will not change under our noses from the
490 vector
<ixfrdiff_t
> toSend
;
491 uint32_t ourLatestSerial
;
493 std::lock_guard
<std::mutex
> guard(g_soas_mutex
);
494 ourLatestSerial
= g_soas
[mdp
.d_qname
].soa
->d_st
.serial
;
497 if (rfc1982LessThan(ourLatestSerial
, clientSOA
->d_st
.serial
) || ourLatestSerial
== clientSOA
->d_st
.serial
){
498 /* RFC 1995 Section 2
499 * If an IXFR query with the same or newer version number than that of
500 * the server is received, it is replied to with a single SOA record of
501 * the server's current version, just as in AXFR.
503 vector
<uint8_t> packet
;
504 bool ret
= makeSOAPacket(mdp
, packet
);
506 packets
.push_back(packet
);
512 // as we use push_back in the updater, we know the vector is sorted as oldest first
513 bool shouldAdd
= false;
514 // Get all relevant IXFR differences
515 std::lock_guard
<std::mutex
> guard(g_soas_mutex
);
516 for (const auto& diff
: g_soas
[mdp
.d_qname
].ixfrDiffs
) {
518 toSend
.push_back(diff
);
521 if (diff
.oldSOA
->d_st
.serial
== clientSOA
->d_st
.serial
) {
522 toSend
.push_back(diff
);
523 // Add all consecutive diffs
529 if (toSend
.empty()) {
530 g_log
<<Logger::Warning
<<"No IXFR available from serial "<<clientSOA
->d_st
.serial
<<" for zone "<<mdp
.d_qname
<<", attempting to send AXFR"<<endl
;
531 return makeAXFRPackets(mdp
, packets
);
534 for (const auto& diff
: toSend
) {
535 /* An IXFR packet's ANSWER section looks as follows:
538 * ... removed records ...
540 * ... added records ...
543 packets
.push_back(getSOAPacket(mdp
, diff
.newSOA
));
544 packets
.push_back(getSOAPacket(mdp
, diff
.oldSOA
));
545 makeXFRPacketsFromDNSRecords(mdp
, diff
.removals
, packets
);
546 packets
.push_back(getSOAPacket(mdp
, diff
.newSOA
));
547 makeXFRPacketsFromDNSRecords(mdp
, diff
.additions
, packets
);
548 packets
.push_back(getSOAPacket(mdp
, diff
.newSOA
));
554 bool allowedByACL(const ComboAddress
& addr
) {
555 return g_acl
.match(addr
);
558 void handleUDPRequest(int fd
, boost::any
&) {
559 // TODO make the buffer-size configurable
562 socklen_t fromlen
= sizeof(saddr
);
563 int res
= recvfrom(fd
, buf
, sizeof(buf
), 0, (struct sockaddr
*) &saddr
, &fromlen
);
566 g_log
<<Logger::Warning
<<"Got an empty message from "<<saddr
.toStringWithPort()<<endl
;
571 auto savedErrno
= errno
;
572 g_log
<<Logger::Warning
<<"Could not read message from "<<saddr
.toStringWithPort()<<": "<<strerror(savedErrno
)<<endl
;
576 if (saddr
== ComboAddress("0.0.0.0", 0)) {
577 g_log
<<Logger::Warning
<<"Could not determine source of message"<<endl
;
581 if (!allowedByACL(saddr
)) {
582 g_log
<<Logger::Warning
<<"UDP query from "<<saddr
.toString()<<" is not allowed, dropping"<<endl
;
586 MOADNSParser
mdp(true, string(buf
, res
));
587 if (!checkQuery(mdp
, saddr
)) {
591 /* RFC 1995 Section 2
592 * Transport of a query may be by either UDP or TCP. If an IXFR query
593 * is via UDP, the IXFR server may attempt to reply using UDP if the
594 * entire response can be contained in a single DNS packet. If the UDP
595 * reply does not fit, the query is responded to with a single SOA
596 * record of the server's current version to inform the client that a
597 * TCP query should be initiated.
599 * Let's not complicate this with IXFR over UDP (and looking if we need to truncate etc).
600 * Just send the current SOA and let the client try over TCP
602 vector
<uint8_t> packet
;
603 makeSOAPacket(mdp
, packet
);
604 if(sendto(fd
, &packet
[0], packet
.size(), 0, (struct sockaddr
*) &saddr
, fromlen
) < 0) {
605 auto savedErrno
= errno
;
606 g_log
<<Logger::Warning
<<"Could not send reply for "<<mdp
.d_qname
<<"|"<<QType(mdp
.d_qtype
).getName()<<" to "<<saddr
.toStringWithPort()<<": "<<strerror(savedErrno
)<<endl
;
611 void handleTCPRequest(int fd
, boost::any
&) {
616 cfd
= SAccept(fd
, saddr
);
618 } catch(runtime_error
&e
) {
619 g_log
<<Logger::Error
<<e
.what()<<endl
;
623 if (saddr
== ComboAddress("0.0.0.0", 0)) {
624 g_log
<<Logger::Warning
<<"Could not determine source of message"<<endl
;
629 if (!allowedByACL(saddr
)) {
630 g_log
<<Logger::Warning
<<"TCP query from "<<saddr
.toString()<<" is not allowed, dropping"<<endl
;
636 std::lock_guard
<std::mutex
> lg(g_tcpRequestFDsMutex
);
637 g_tcpRequestFDs
.push({cfd
, saddr
});
639 g_tcpHandlerCV
.notify_one();
642 /* Thread to handle TCP traffic
644 void tcpWorker(int tid
) {
645 string prefix
= "TCP Worker " + std::to_string(tid
) + ": ";
648 g_log
<<Logger::Debug
<<prefix
<<"ready for a new request!"<<endl
;
649 std::unique_lock
<std::mutex
> lk(g_tcpRequestFDsMutex
);
650 g_tcpHandlerCV
.wait(lk
, []{return g_tcpRequestFDs
.size() || g_exiting
;});
652 g_log
<<Logger::Debug
<<prefix
<<"Stopping thread"<<endl
;
655 g_log
<<Logger::Debug
<<prefix
<<"Going to handle a query"<<endl
;
656 auto request
= g_tcpRequestFDs
.front();
657 g_tcpRequestFDs
.pop();
660 int cfd
= request
.first
;
661 ComboAddress saddr
= request
.second
;
667 readn2(cfd
, &toRead
, sizeof(toRead
));
668 toRead
= std::min(ntohs(toRead
), static_cast<uint16_t>(sizeof(buf
)));
669 res
= readn2WithTimeout(cfd
, &buf
, toRead
, 2);
670 g_log
<<Logger::Debug
<<prefix
<<"Had message of "<<std::to_string(toRead
)<<" bytes from "<<saddr
.toStringWithPort()<<endl
;
671 } catch (runtime_error
&e
) {
672 g_log
<<Logger::Warning
<<prefix
<<"Could not read message from "<<saddr
.toStringWithPort()<<": "<<e
.what()<<endl
;
678 MOADNSParser
mdp(true, string(buf
, res
));
680 if (!checkQuery(mdp
, saddr
, false, prefix
)) {
685 vector
<vector
<uint8_t>> packets
;
686 if (mdp
.d_qtype
== QType::SOA
) {
687 vector
<uint8_t> packet
;
688 bool ret
= makeSOAPacket(mdp
, packet
);
693 packets
.push_back(packet
);
696 if (mdp
.d_qtype
== QType::AXFR
) {
697 if (!makeAXFRPackets(mdp
, packets
)) {
703 if (mdp
.d_qtype
== QType::IXFR
) {
704 /* RFC 1995 section 3:
705 * The IXFR query packet format is the same as that of a normal DNS
706 * query, but with the query type being IXFR and the authority section
707 * containing the SOA record of client's version of the zone.
709 shared_ptr
<SOARecordContent
> clientSOA
;
710 for (auto &answer
: mdp
.d_answers
) {
711 // from dnsparser.hh:
712 // typedef vector<pair<DNSRecord, uint16_t > > answers_t;
713 if (answer
.first
.d_type
== QType::SOA
&& answer
.first
.d_place
== DNSResourceRecord::AUTHORITY
) {
714 clientSOA
= getRR
<SOARecordContent
>(answer
.first
);
715 if (clientSOA
!= nullptr) {
719 } /* for (auto const &answer : mdp.d_answers) */
721 if (clientSOA
== nullptr) {
722 g_log
<<Logger::Warning
<<prefix
<<"IXFR request packet did not contain a SOA record in the AUTHORITY section"<<endl
;
727 if (!makeIXFRPackets(mdp
, clientSOA
, packets
)) {
731 } /* if (mdp.d_qtype == QType::IXFR) */
733 g_log
<<Logger::Debug
<<prefix
<<"Sending "<<packets
.size()<<" packets to "<<saddr
.toStringWithPort()<<endl
;
734 for (const auto& packet
: packets
) {
736 sendBuf
[0]=packet
.size()/256;
737 sendBuf
[1]=packet
.size()%256;
739 ssize_t send
= writen2(cfd
, sendBuf
, 2);
740 send
+= writen2(cfd
, &packet
[0], packet
.size());
743 } catch (MOADNSException
&e
) {
744 g_log
<<Logger::Warning
<<prefix
<<"Could not parse DNS packet from "<<saddr
.toStringWithPort()<<": "<<e
.what()<<endl
;
745 } catch (runtime_error
&e
) {
746 g_log
<<Logger::Warning
<<prefix
<<"Could not write reply to "<<saddr
.toStringWithPort()<<": "<<e
.what()<<endl
;
757 /* Parses the configuration file in configpath into config, adding defaults for
758 * missing parameters (if applicable), returning true if the config file was
759 * good, false otherwise. Will log all issues with the config
761 bool parseAndCheckConfig(const string
& configpath
, YAML::Node
& config
) {
762 g_log
<<Logger::Info
<<"Loading configuration file from "<<configpath
<<endl
;
764 config
= YAML::LoadFile(configpath
);
765 } catch (const runtime_error
&e
) {
766 g_log
<<Logger::Error
<<"Unable to load configuration file '"<<configpath
<<"': "<<e
.what()<<endl
;
772 if (config
["keep"]) {
774 config
["keep"].as
<uint16_t>();
775 } catch (const runtime_error
&e
) {
776 g_log
<<Logger::Error
<<"Unable to read 'keep' value: "<<e
.what()<<endl
;
783 if (config
["axfr-timeout"]) {
785 config
["axfr-timeout"].as
<uint16_t>();
786 } catch (const runtime_error
&e
) {
787 g_log
<<Logger::Error
<<"Unable to read 'axfr-timeout' value: "<<e
.what()<<endl
;
790 config
["axfr-timeout"] = 10;
793 if (config
["tcp-in-threads"]) {
795 config
["tcp-in-threads"].as
<uint16_t>();
796 } catch (const runtime_error
&e
) {
797 g_log
<<Logger::Error
<<"Unable to read 'tcp-in-threads' value: "<<e
.what()<<endl
;
800 config
["tcp-in-threads"] = 10;
803 if (config
["listen"]) {
805 config
["listen"].as
<vector
<ComboAddress
>>();
806 } catch (const runtime_error
&e
) {
807 g_log
<<Logger::Error
<<"Unable to read 'listen' value: "<<e
.what()<<endl
;
811 config
["listen"].push_back("127.0.0.1:53");
812 config
["listen"].push_back("[::1]:53");
817 config
["acl"].as
<vector
<string
>>();
818 } catch (const runtime_error
&e
) {
819 g_log
<<Logger::Error
<<"Unable to read 'acl' value: "<<e
.what()<<endl
;
823 config
["acl"].push_back("127.0.0.0/8");
824 config
["acl"].push_back("::1/128");
827 if (config
["work-dir"]) {
829 config
["work-dir"].as
<string
>();
830 } catch(const runtime_error
&e
) {
831 g_log
<<Logger::Error
<<"Unable to read 'work-dir' value: "<<e
.what()<<endl
;
836 config
["work-dir"] = getcwd(tmp
, sizeof(tmp
)) ? string(tmp
) : "";;
841 config
["uid"].as
<string
>();
842 } catch(const runtime_error
&e
) {
843 g_log
<<Logger::Error
<<"Unable to read 'uid' value: "<<e
.what()<<endl
;
850 config
["gid"].as
<string
>();
851 } catch(const runtime_error
&e
) {
852 g_log
<<Logger::Error
<<"Unable to read 'gid' value: "<<e
.what()<<endl
;
857 if (config
["domains"]) {
858 if (config
["domains"].size() == 0) {
859 g_log
<<Logger::Error
<<"No domains configured"<<endl
;
862 for (auto const &domain
: config
["domains"]) {
864 if (!domain
["domain"]) {
865 g_log
<<Logger::Error
<<"An entry in 'domains' is missing a 'domain' key!"<<endl
;
869 domain
["domain"].as
<DNSName
>();
870 } catch (const runtime_error
&e
) {
871 g_log
<<Logger::Error
<<"Unable to read domain '"<<domain
["domain"].as
<string
>()<<"': "<<e
.what()<<endl
;
874 if (!domain
["master"]) {
875 g_log
<<Logger::Error
<<"Domain '"<<domain
["domain"].as
<string
>()<<"' has no master configured!"<<endl
;
879 domain
["master"].as
<ComboAddress
>();
880 } catch (const runtime_error
&e
) {
881 g_log
<<Logger::Error
<<"Unable to read domain '"<<domain
["domain"].as
<string
>()<<"' master address: "<<e
.what()<<endl
;
886 g_log
<<Logger::Error
<<"No domains configured"<<endl
;
893 int main(int argc
, char** argv
) {
894 g_log
.setLoglevel(Logger::Notice
);
895 g_log
.toConsole(Logger::Notice
);
896 g_log
.setPrefixed(true);
897 g_log
.disableSyslog(true);
898 g_log
.setTimestamps(false);
899 po::variables_map g_vm
;
901 po::options_description
desc("IXFR distribution tool");
903 ("help", "produce help message")
904 ("version", "Display the version of ixfrdist")
905 ("verbose", "Be verbose")
906 ("debug", "Be even more verbose")
907 ("config", po::value
<string
>()->default_value(SYSCONFDIR
+ string("/ixfrdist.yml")), "Configuration file to use")
910 po::store(po::command_line_parser(argc
, argv
).options(desc
).run(), g_vm
);
913 if (g_vm
.count("help") > 0) {
918 if (g_vm
.count("version") > 0) {
919 cout
<<"ixfrdist "<<VERSION
<<endl
;
922 } catch (po::error
&e
) {
923 g_log
<<Logger::Error
<<e
.what()<<". See `ixfrdist --help` for valid options"<<endl
;
924 return(EXIT_FAILURE
);
927 bool had_error
= false;
929 if (g_vm
.count("verbose")) {
930 g_log
.setLoglevel(Logger::Info
);
931 g_log
.toConsole(Logger::Info
);
934 if (g_vm
.count("debug") > 0) {
935 g_log
.setLoglevel(Logger::Debug
);
936 g_log
.toConsole(Logger::Debug
);
939 g_log
<<Logger::Notice
<<"IXFR distributor version "<<VERSION
<<" starting up!"<<endl
;
942 if (!parseAndCheckConfig(g_vm
["config"].as
<string
>(), config
)) {
943 // parseAndCheckConfig already logged whatever was wrong
947 /* From hereon out, we known that all the values in config are valid. */
949 for (auto const &domain
: config
["domains"]) {
951 s
.insert(domain
["master"].as
<ComboAddress
>());
952 g_domainConfigs
[domain
["domain"].as
<DNSName
>()].masters
= s
;
955 for (const auto &addr
: config
["acl"].as
<vector
<string
>>()) {
958 } catch (const NetmaskException
&e
) {
959 g_log
<<Logger::Error
<<e
.reason
<<endl
;
963 g_log
<<Logger::Notice
<<"ACL set to "<<g_acl
.toString()<<"."<<endl
;
965 FDMultiplexer
* fdm
= FDMultiplexer::getMultiplexerSilent();
966 if (fdm
== nullptr) {
967 g_log
<<Logger::Error
<<"Could not enable a multiplexer for the listen sockets!"<<endl
;
972 for (const auto& addr
: config
["listen"].as
<vector
<ComboAddress
>>()) {
973 for (const auto& stype
: {SOCK_DGRAM
, SOCK_STREAM
}) {
975 int s
= SSocket(addr
.sin4
.sin_family
, stype
, 0);
979 if (stype
== SOCK_STREAM
) {
980 SListen(s
, 30); // TODO make this configurable
982 fdm
->addReadFD(s
, stype
== SOCK_DGRAM
? handleUDPRequest
: handleTCPRequest
);
983 allSockets
.insert(s
);
984 } catch(runtime_error
&e
) {
985 g_log
<<Logger::Error
<<e
.what()<<endl
;
995 string gid
= config
["gid"].as
<string
>();
996 if (!(newgid
= atoi(gid
.c_str()))) {
997 struct group
*gr
= getgrnam(gid
.c_str());
999 g_log
<<Logger::Error
<<"Can not determine group-id for gid "<<gid
<<endl
;
1002 newgid
= gr
->gr_gid
;
1005 g_log
<<Logger::Notice
<<"Dropping effective group-id to "<<newgid
<<endl
;
1006 if (setgid(newgid
) < 0) {
1007 g_log
<<Logger::Error
<<"Could not set group id to "<<newgid
<<": "<<stringerror()<<endl
;
1014 if (config
["uid"]) {
1015 string uid
= config
["uid"].as
<string
>();
1016 if (!(newuid
= atoi(uid
.c_str()))) {
1017 struct passwd
*pw
= getpwnam(uid
.c_str());
1018 if (pw
== nullptr) {
1019 g_log
<<Logger::Error
<<"Can not determine user-id for uid "<<uid
<<endl
;
1022 newuid
= pw
->pw_uid
;
1026 struct passwd
*pw
= getpwuid(newuid
);
1027 if (pw
== nullptr) {
1028 if (setgroups(0, nullptr) < 0) {
1029 g_log
<<Logger::Error
<<"Unable to drop supplementary gids: "<<stringerror()<<endl
;
1033 if (initgroups(pw
->pw_name
, newgid
) < 0) {
1034 g_log
<<Logger::Error
<<"Unable to set supplementary groups: "<<stringerror()<<endl
;
1039 g_log
<<Logger::Notice
<<"Dropping effective user-id to "<<newuid
<<endl
;
1040 if (setuid(newuid
) < 0) {
1041 g_log
<<Logger::Error
<<"Could not set user id to "<<newuid
<<": "<<stringerror()<<endl
;
1047 // We have already sent the errors to stderr, just die
1048 return EXIT_FAILURE
;
1051 // It all starts here
1052 signal(SIGTERM
, handleSignal
);
1053 signal(SIGINT
, handleSignal
);
1054 signal(SIGSTOP
, handleSignal
);
1055 signal(SIGPIPE
, SIG_IGN
);
1057 // Init the things we need
1062 std::thread
ut(updateThread
,
1063 config
["work-dir"].as
<string
>(),
1064 config
["keep"].as
<uint16_t>(),
1065 config
["axfr-timeout"].as
<uint16_t>());
1067 vector
<std::thread
> tcpHandlers
;
1068 tcpHandlers
.reserve(config
["tcp-in-threads"].as
<uint16_t>());
1069 for (size_t i
= 0; i
< tcpHandlers
.capacity(); ++i
) {
1070 tcpHandlers
.push_back(std::thread(tcpWorker
, i
));
1075 gettimeofday(&now
, 0);
1078 g_log
<<Logger::Debug
<<"Closing listening sockets"<<endl
;
1079 for (const int& fd
: allSockets
) {
1082 } catch(PDNSException
&e
) {
1083 g_log
<<Logger::Error
<<e
.reason
<<endl
;
1089 g_log
<<Logger::Debug
<<"Waiting for al threads to stop"<<endl
;
1090 g_tcpHandlerCV
.notify_all();
1092 for (auto &t
: tcpHandlers
) {
1095 g_log
<<Logger::Notice
<<"IXFR distributor stopped"<<endl
;
1096 return EXIT_SUCCESS
;