]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ixplore.cc
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 "arguments.hh"
27 #include <sys/types.h>
30 #include "dnsparser.hh"
33 #include "dnswriter.hh"
34 #include "dnsrecords.hh"
37 #include "dnssecinfra.hh"
39 #include "dns_random.hh"
40 #include "gss_context.hh"
41 #include "zoneparser-tng.hh"
42 #include <boost/multi_index_container.hpp>
43 #include "resolver.hh"
46 using namespace boost::multi_index
;
51 static ArgvMap theArg
;
56 struct CIContentCompareStruct
58 bool operator()(const shared_ptr
<DNSRecordContent
>&a
, const shared_ptr
<DNSRecordContent
>& b
) const
60 return toLower(a
->getZoneRepresentation()) < toLower(b
->getZoneRepresentation());
65 typedef multi_index_container
<
69 composite_key
<DNSRecord
,
70 member
<DNSRecord
, DNSName
, &DNSRecord::d_name
>,
71 member
<DNSRecord
, uint16_t, &DNSRecord::d_type
>,
72 member
<DNSRecord
, uint16_t, &DNSRecord::d_class
>,
73 member
<DNSRecord
, shared_ptr
<DNSRecordContent
>, &DNSRecord::d_content
> >,
74 composite_key_compare
<CanonDNSNameCompare
, std::less
<uint16_t>, std::less
<uint16_t>, CIContentCompareStruct
>
80 uint32_t getSerialFromMaster(const ComboAddress
& master
, const DNSName
& zone
, shared_ptr
<SOARecordContent
>& sr
, const TSIGTriplet
& tt
= TSIGTriplet())
82 vector
<uint8_t> packet
;
83 DNSPacketWriter
pw(packet
, zone
, QType::SOA
);
84 if(!tt
.algo
.empty()) {
85 TSIGRecordContent trc
;
86 trc
.d_algoName
= tt
.algo
;
87 trc
.d_time
= time((time_t*)NULL
);
89 trc
.d_origID
=ntohs(pw
.getHeader()->id
);
91 addTSIG(pw
, &trc
, tt
.name
, tt
.secret
, "", false);
94 Socket
s(master
.sin4
.sin_family
, SOCK_DGRAM
);
96 string
msg((const char*)&packet
[0], packet
.size());
101 MOADNSParser
mdp(false, reply
);
102 if(mdp
.d_header
.rcode
) {
103 throw std::runtime_error("Unable to retrieve SOA serial from master '"+master
.toStringWithPort()+"': "+RCode::to_s(mdp
.d_header
.rcode
));
105 for(const auto& r
: mdp
.d_answers
) {
106 if(r
.first
.d_type
== QType::SOA
) {
107 sr
= std::dynamic_pointer_cast
<SOARecordContent
>(r
.first
.d_content
);
108 return sr
->d_st
.serial
;
115 uint32_t getSerialsFromDir(const std::string
& dir
)
118 DIR* dirhdl
=opendir(dir
.c_str());
120 throw runtime_error("Could not open IXFR directory");
121 struct dirent
*entry
;
123 while((entry
= readdir(dirhdl
))) {
124 uint32_t num
= atoi(entry
->d_name
);
125 if(std::to_string(num
) == entry
->d_name
)
132 uint32_t getSerialFromRecords(const records_t
& records
, DNSRecord
& soaret
)
135 uint16_t t
=QType::SOA
;
137 auto found
= records
.equal_range(tie(root
, t
));
139 for(auto iter
= found
.first
; iter
!= found
.second
; ++iter
) {
140 auto soa
= std::dynamic_pointer_cast
<SOARecordContent
>(iter
->d_content
);
142 return soa
->d_st
.serial
;
147 void writeZoneToDisk(const records_t
& records
, const DNSName
& zone
, const std::string
& directory
)
150 int serial
= getSerialFromRecords(records
, soa
);
151 string fname
=directory
+"/"+std::to_string(serial
);
152 FILE* fp
=fopen((fname
+".partial").c_str(), "w");
154 throw runtime_error("Unable to open file '"+fname
+".partial' for writing: "+string(strerror(errno
)));
157 soarecord
.insert(soa
);
158 fprintf(fp
, "$ORIGIN %s\n", zone
.toString().c_str());
159 for(const auto& outer
: {soarecord
, records
, soarecord
} ) {
160 for(const auto& r
: outer
) {
161 fprintf(fp
, "%s\tIN\t%s\t%s\n",
162 r
.d_name
.isRoot() ? "@" : r
.d_name
.toStringNoDot().c_str(),
163 DNSRecordContent::NumberToType(r
.d_type
).c_str(),
164 r
.d_content
->getZoneRepresentation().c_str());
168 rename( (fname
+".partial").c_str(), fname
.c_str());
171 void loadZoneFromDisk(records_t
& records
, const string
& fname
, const DNSName
& zone
)
173 ZoneParserTNG
zpt(fname
, zone
);
175 DNSResourceRecord rr
;
177 unsigned int nrecords
=0;
180 if(rr
.qtype
.getCode() == QType::CNAME
&& rr
.content
.empty())
182 rr
.qname
= rr
.qname
.makeRelative(zone
);
184 if(rr
.qtype
.getCode() != QType::SOA
|| seenSOA
==false)
185 records
.insert(DNSRecord(rr
));
186 if(rr
.qtype
.getCode() == QType::SOA
) {
190 cout
<<"Parsed "<<nrecords
<<" records"<<endl
;
191 if(rr
.qtype
.getCode() == QType::SOA
&& seenSOA
) {
192 cout
<<"Zone was complete (SOA at end)"<<endl
;
196 throw runtime_error("Zone not complete!");
201 cerr
<<"Syntax: ixplore diff ZONE BEFORE_FILE AFTER_FILE"<<endl
;
202 cerr
<<"Syntax: ixplore track IP-ADDRESS PORT ZONE DIRECTORY [TSIGKEY TSIGALGO TSIGSECRET]"<<endl
;
205 int main(int argc
, char** argv
)
208 for(int n
=1 ; n
< argc
; ++n
) {
209 if ((string
) argv
[n
] == "--help") {
214 if ((string
) argv
[n
] == "--version") {
215 cerr
<<"ixplore "<<VERSION
<<endl
;
222 if(argc
< 5 || (command
=argv
[1], (command
!="diff" && command
!="track"))) {
226 if(command
=="diff") {
227 records_t before
, after
;
228 DNSName
zone(argv
[2]);
229 cout
<<"Loading before from "<<argv
[3]<<endl
;
230 loadZoneFromDisk(before
, argv
[3], zone
);
231 cout
<<"Loading after from "<<argv
[4]<<endl
;
232 loadZoneFromDisk(after
, argv
[4], zone
);
234 vector
<DNSRecord
> diff
;
236 set_difference(before
.cbegin(), before
.cend(), after
.cbegin(), after
.cend(), back_inserter(diff
), before
.value_comp());
237 for(const auto& d
: diff
) {
238 cout
<<'-'<< (d
.d_name
+zone
) <<" IN "<<DNSRecordContent::NumberToType(d
.d_type
)<<" "<<d
.d_content
->getZoneRepresentation()<<endl
;
241 set_difference(after
.cbegin(), after
.cend(), before
.cbegin(), before
.cend(), back_inserter(diff
), before
.value_comp());
242 for(const auto& d
: diff
) {
243 cout
<<'+'<< (d
.d_name
+zone
) <<" IN "<<DNSRecordContent::NumberToType(d
.d_type
)<<" "<<d
.d_content
->getZoneRepresentation()<<endl
;
248 // must be "track" then
251 in directory/zone-name we leave files with their name the serial number
252 at startup, retrieve current SOA SERIAL for domain from master server
254 compare with what the best is we have in our directory, IXFR from that.
255 Store result in memory, read that best zone in memory, apply deltas, write it out.
257 Next up, loop this every REFRESH seconds */
258 dns_random_init("0123456789abcdef");
260 DNSName
zone(argv
[4]);
261 ComboAddress
master(argv
[2], atoi(argv
[3]));
262 string
directory(argv
[5]);
265 uint32_t ourSerial
= getSerialsFromDir(directory
);
267 cout
<<"Loading zone, our highest available serial is "<< ourSerial
<<endl
;
271 tt
.name
=DNSName(toLower(argv
[6]));
273 tt
.algo
=DNSName(toLower(argv
[7]));
276 if(B64Decode(argv
[8], tt
.secret
) < 0) {
277 cerr
<<"Could not decode tsig secret!"<<endl
;
284 throw std::runtime_error("There is no local zone available");
285 string fname
=directory
+"/"+std::to_string(ourSerial
);
286 cout
<<"Loading serial number "<<ourSerial
<<" from file "<<fname
<<endl
;
287 loadZoneFromDisk(records
, fname
, zone
);
289 catch(std::exception
& e
) {
290 cout
<<"Could not load zone from disk: "<<e
.what()<<endl
;
291 cout
<<"Retrieving latest from master "<<master
.toStringWithPort()<<endl
;
292 ComboAddress local
= master
.sin4
.sin_family
== AF_INET
? ComboAddress("0.0.0.0") : ComboAddress("::");
293 AXFRRetriever
axfr(master
, zone
, tt
, &local
);
294 unsigned int nrecords
=0;
296 vector
<DNSRecord
> chunk
;
297 char wheel
[]="|/-\\";
300 while(axfr
.getChunk(nop
, &chunk
)) {
301 for(auto& dr
: chunk
) {
302 if(dr
.d_type
== QType::TSIG
)
304 dr
.d_name
.makeUsRelative(zone
);
309 if(last
!= time(0)) {
310 cout
<< '\r' << wheel
[count
% (sizeof(wheel
)-1)] << ' ' <<nrecords
;
316 cout
<<"\rDone, got "<<nrecords
<<" "<<endl
;
317 cout
<<"Writing to disk.."<<endl
;
318 writeZoneToDisk(records
, zone
, directory
);
323 ourSerial
= getSerialFromRecords(records
, ourSoa
);
325 cout
<<"Checking for update, our serial number is "<<ourSerial
<<".. ";
327 shared_ptr
<SOARecordContent
> sr
;
328 uint32_t serial
= getSerialFromMaster(master
, zone
, sr
, tt
);
329 if(ourSerial
== serial
) {
330 cout
<<"still up to date, their serial is "<<serial
<<", sleeping "<<sr
->d_st
.refresh
<<" seconds"<<endl
;
331 sleep(sr
->d_st
.refresh
);
335 cout
<<"got new serial: "<<serial
<<", initiating IXFR!"<<endl
;
336 auto deltas
= getIXFRDeltas(master
, zone
, ourSoa
, tt
);
337 cout
<<"Got "<<deltas
.size()<<" deltas, applying.."<<endl
;
339 for(const auto& delta
: deltas
) {
341 const auto& remove
= delta
.first
;
342 const auto& add
= delta
.second
;
344 ourSerial
=getSerialFromRecords(records
, ourSoa
);
345 uint32_t newserial
=0;
346 for(const auto& rr
: add
) {
347 if(rr
.d_type
== QType::SOA
) {
348 newserial
=std::dynamic_pointer_cast
<SOARecordContent
>(rr
.d_content
)->d_st
.serial
;
352 cout
<<"This delta ("<<ourSerial
<<" - "<<newserial
<<") has "<<remove
.size()<<" removals, "<<add
.size()<<" additions"<<endl
;
353 ofstream
report(directory
+"/delta."+std::to_string(ourSerial
)+"-"+std::to_string(newserial
));
355 cout
<<"This delta is a whole new zone"<<endl
;
356 report
<<"- everything, whole new zone update follow"<<endl
;
362 for(const auto& rr
: remove
) {
363 report
<<'-'<< (rr
.d_name
+zone
) <<" IN "<<DNSRecordContent::NumberToType(rr
.d_type
)<<" "<<rr
.d_content
->getZoneRepresentation()<<endl
;
364 auto range
= records
.equal_range(tie(rr
.d_name
, rr
.d_type
, rr
.d_class
, rr
.d_content
));
365 if(range
.first
== range
.second
) {
366 cout
<<endl
<<" !! Could not find record "<<rr
.d_name
<<" to remove!!"<<endl
;
370 records
.erase(range
.first
, range
.second
);
373 for(const auto& rr
: add
) {
374 report
<<'+'<< (rr
.d_name
+zone
) <<" IN "<<DNSRecordContent::NumberToType(rr
.d_type
)<<" "<<rr
.d_content
->getZoneRepresentation()<<endl
;
378 cerr
<<"Had error condition, stopping.."<<endl
;
383 cout
<<"Writing zone to disk.. "; cout
.flush();
384 writeZoneToDisk(records
, zone
, directory
);
388 catch(PDNSException
&e2
) {
389 cerr
<<"Fatal: "<<e2
.reason
<<endl
;
391 catch(std::exception
&e
)
393 cerr
<<"Fatal: "<<e
.what()<<endl
;
397 cerr
<<"Any other exception"<<endl
;