]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ixplore.cc
8646eaf254d4828088afec565c35353044123065
4 #include "arguments.hh"
9 #include "dnsparser.hh"
12 #include "dnswriter.hh"
13 #include "dnsrecords.hh"
16 #include "dnssecinfra.hh"
18 #include "dns_random.hh"
19 #include "gss_context.hh"
20 #include "zoneparser-tng.hh"
21 #include <boost/multi_index_container.hpp>
22 #include "resolver.hh"
25 using namespace boost::multi_index
;
30 static ArgvMap theArg
;
35 struct CIContentCompareStruct
37 bool operator()(const shared_ptr
<DNSRecordContent
>&a
, const shared_ptr
<DNSRecordContent
>& b
) const
39 return toLower(a
->getZoneRepresentation()) < toLower(b
->getZoneRepresentation());
44 typedef multi_index_container
<
48 composite_key
<DNSRecord
,
49 member
<DNSRecord
, DNSName
, &DNSRecord::d_name
>,
50 member
<DNSRecord
, uint16_t, &DNSRecord::d_type
>,
51 member
<DNSRecord
, uint16_t, &DNSRecord::d_class
>,
52 member
<DNSRecord
, shared_ptr
<DNSRecordContent
>, &DNSRecord::d_content
> >,
53 composite_key_compare
<CanonDNSNameCompare
, std::less
<uint16_t>, std::less
<uint16_t>, CIContentCompareStruct
>
59 uint32_t getSerialFromMaster(const ComboAddress
& master
, const DNSName
& zone
, shared_ptr
<SOARecordContent
>& sr
, const TSIGTriplet
& tt
= TSIGTriplet())
61 vector
<uint8_t> packet
;
62 DNSPacketWriter
pw(packet
, zone
, QType::SOA
);
63 if(!tt
.algo
.empty()) {
64 TSIGRecordContent trc
;
65 trc
.d_algoName
= tt
.algo
;
66 trc
.d_time
= time((time_t*)NULL
);
68 trc
.d_origID
=ntohs(pw
.getHeader()->id
);
70 addTSIG(pw
, &trc
, tt
.name
, tt
.secret
, "", false);
73 Socket
s(master
.sin4
.sin_family
, SOCK_DGRAM
);
75 string
msg((const char*)&packet
[0], packet
.size());
80 MOADNSParser
mdp(reply
);
81 if(mdp
.d_header
.rcode
) {
82 throw std::runtime_error("Unable to retrieve SOA serial from master '"+master
.toStringWithPort()+"': "+RCode::to_s(mdp
.d_header
.rcode
));
84 for(const auto& r
: mdp
.d_answers
) {
85 if(r
.first
.d_type
== QType::SOA
) {
86 sr
= std::dynamic_pointer_cast
<SOARecordContent
>(r
.first
.d_content
);
87 return sr
->d_st
.serial
;
94 uint32_t getSerialsFromDir(const std::string
& dir
)
97 DIR* dirhdl
=opendir(dir
.c_str());
99 throw runtime_error("Could not open IXFR directory");
100 struct dirent
*entry
;
102 while((entry
= readdir(dirhdl
))) {
103 uint32_t num
= atoi(entry
->d_name
);
104 if(std::to_string(num
) == entry
->d_name
)
111 uint32_t getSerialFromRecords(const records_t
& records
, DNSRecord
& soaret
)
114 uint16_t t
=QType::SOA
;
116 auto found
= records
.equal_range(tie(root
, t
));
118 for(auto iter
= found
.first
; iter
!= found
.second
; ++iter
) {
119 auto soa
= std::dynamic_pointer_cast
<SOARecordContent
>(iter
->d_content
);
121 return soa
->d_st
.serial
;
126 void writeZoneToDisk(const records_t
& records
, const DNSName
& zone
, const std::string
& directory
)
129 int serial
= getSerialFromRecords(records
, soa
);
130 string fname
=directory
+"/"+std::to_string(serial
);
131 FILE* fp
=fopen((fname
+".partial").c_str(), "w");
133 throw runtime_error("Unable to open file '"+fname
+".partial' for writing: "+string(strerror(errno
)));
136 soarecord
.insert(soa
);
137 fprintf(fp
, "$ORIGIN %s\n", zone
.toString().c_str());
138 for(const auto& outer
: {soarecord
, records
, soarecord
} ) {
139 for(const auto& r
: outer
) {
140 fprintf(fp
, "%s\tIN\t%s\t%s\n",
141 r
.d_name
.isRoot() ? "@" : r
.d_name
.toStringNoDot().c_str(),
142 DNSRecordContent::NumberToType(r
.d_type
).c_str(),
143 r
.d_content
->getZoneRepresentation().c_str());
147 rename( (fname
+".partial").c_str(), fname
.c_str());
150 void loadZoneFromDisk(records_t
& records
, const string
& fname
, const DNSName
& zone
)
152 ZoneParserTNG
zpt(fname
, zone
);
154 DNSResourceRecord rr
;
156 unsigned int nrecords
=0;
159 if(rr
.qtype
.getCode() == QType::CNAME
&& rr
.content
.empty())
161 rr
.qname
= rr
.qname
.makeRelative(zone
);
163 if(rr
.qtype
.getCode() != QType::SOA
|| seenSOA
==false)
164 records
.insert(DNSRecord(rr
));
165 if(rr
.qtype
.getCode() == QType::SOA
) {
169 cout
<<"Parsed "<<nrecords
<<" records"<<endl
;
170 if(rr
.qtype
.getCode() == QType::SOA
&& seenSOA
) {
171 cout
<<"Zone was complete (SOA at end)"<<endl
;
175 throw runtime_error("Zone not complete!");
180 cerr
<<"Syntax: ixplore diff ZONE BEFORE_FILE AFTER_FILE"<<endl
;
181 cerr
<<"Syntax: ixplore track IP-ADDRESS PORT ZONE DIRECTORY [TSIGKEY TSIGALGO TSIGSECRET]"<<endl
;
184 int main(int argc
, char** argv
)
187 for(int n
=1 ; n
< argc
; ++n
) {
188 if ((string
) argv
[n
] == "--help") {
193 if ((string
) argv
[n
] == "--version") {
194 cerr
<<"ixplore "<<VERSION
<<endl
;
201 if(argc
< 5 || (command
=argv
[1], (command
!="diff" && command
!="track"))) {
205 if(command
=="diff") {
206 records_t before
, after
;
207 DNSName
zone(argv
[2]);
208 cout
<<"Loading before from "<<argv
[3]<<endl
;
209 loadZoneFromDisk(before
, argv
[3], zone
);
210 cout
<<"Loading after from "<<argv
[4]<<endl
;
211 loadZoneFromDisk(after
, argv
[4], zone
);
213 vector
<DNSRecord
> diff
;
215 set_difference(before
.cbegin(), before
.cend(), after
.cbegin(), after
.cend(), back_inserter(diff
), before
.value_comp());
216 for(const auto& d
: diff
) {
217 cout
<<'-'<< (d
.d_name
+zone
) <<" IN "<<DNSRecordContent::NumberToType(d
.d_type
)<<" "<<d
.d_content
->getZoneRepresentation()<<endl
;
220 set_difference(after
.cbegin(), after
.cend(), before
.cbegin(), before
.cend(), back_inserter(diff
), before
.value_comp());
221 for(const auto& d
: diff
) {
222 cout
<<'+'<< (d
.d_name
+zone
) <<" IN "<<DNSRecordContent::NumberToType(d
.d_type
)<<" "<<d
.d_content
->getZoneRepresentation()<<endl
;
227 // must be "track" then
230 in directory/zone-name we leave files with their name the serial number
231 at startup, retrieve current SOA SERIAL for domain from master server
233 compare with what the best is we have in our directory, IXFR from that.
234 Store result in memory, read that best zone in memory, apply deltas, write it out.
236 Next up, loop this every REFRESH seconds */
237 dns_random_init("0123456789abcdef");
239 DNSName
zone(argv
[4]);
240 ComboAddress
master(argv
[2], atoi(argv
[3]));
241 string
directory(argv
[5]);
244 uint32_t ourSerial
= getSerialsFromDir(directory
);
246 cout
<<"Loading zone, our highest available serial is "<< ourSerial
<<endl
;
250 tt
.name
=DNSName(toLower(argv
[6]));
252 tt
.algo
=DNSName(toLower(argv
[7]));
255 if(B64Decode(argv
[8], tt
.secret
) < 0) {
256 cerr
<<"Could not decode tsig secret!"<<endl
;
263 throw std::runtime_error("There is no local zone available");
264 string fname
=directory
+"/"+std::to_string(ourSerial
);
265 cout
<<"Loading serial number "<<ourSerial
<<" from file "<<fname
<<endl
;
266 loadZoneFromDisk(records
, fname
, zone
);
268 catch(std::exception
& e
) {
269 cout
<<"Could not load zone from disk: "<<e
.what()<<endl
;
270 cout
<<"Retrieving latest from master "<<master
.toStringWithPort()<<endl
;
271 ComboAddress local
= master
.sin4
.sin_family
== AF_INET
? ComboAddress("0.0.0.0") : ComboAddress("::");
272 AXFRRetriever
axfr(master
, zone
, tt
, &local
);
273 unsigned int nrecords
=0;
275 vector
<DNSRecord
> chunk
;
276 char wheel
[]="|/-\\";
279 while(axfr
.getChunk(nop
, &chunk
)) {
280 for(auto& dr
: chunk
) {
281 if(dr
.d_type
== QType::TSIG
)
283 dr
.d_name
.makeUsRelative(zone
);
288 if(last
!= time(0)) {
289 cout
<< '\r' << wheel
[count
% (sizeof(wheel
)-1)] << ' ' <<nrecords
;
295 cout
<<"\rDone, got "<<nrecords
<<" "<<endl
;
296 cout
<<"Writing to disk.."<<endl
;
297 writeZoneToDisk(records
, zone
, directory
);
302 ourSerial
= getSerialFromRecords(records
, ourSoa
);
304 cout
<<"Checking for update, our serial number is "<<ourSerial
<<".. ";
306 shared_ptr
<SOARecordContent
> sr
;
307 uint32_t serial
= getSerialFromMaster(master
, zone
, sr
, tt
);
308 if(ourSerial
== serial
) {
309 cout
<<"still up to date, their serial is "<<serial
<<", sleeping "<<sr
->d_st
.refresh
<<" seconds"<<endl
;
310 sleep(sr
->d_st
.refresh
);
314 cout
<<"got new serial: "<<serial
<<", initiating IXFR!"<<endl
;
315 auto deltas
= getIXFRDeltas(master
, zone
, ourSoa
, tt
);
316 cout
<<"Got "<<deltas
.size()<<" deltas, applying.."<<endl
;
318 for(const auto& delta
: deltas
) {
320 const auto& remove
= delta
.first
;
321 const auto& add
= delta
.second
;
323 ourSerial
=getSerialFromRecords(records
, ourSoa
);
324 uint32_t newserial
=0;
325 for(const auto& rr
: add
) {
326 if(rr
.d_type
== QType::SOA
) {
327 newserial
=std::dynamic_pointer_cast
<SOARecordContent
>(rr
.d_content
)->d_st
.serial
;
331 cout
<<"This delta ("<<ourSerial
<<" - "<<newserial
<<") has "<<remove
.size()<<" removals, "<<add
.size()<<" additions"<<endl
;
332 ofstream
report(directory
+"/delta."+std::to_string(ourSerial
)+"-"+std::to_string(newserial
));
334 cout
<<"This delta is a whole new zone"<<endl
;
335 report
<<"- everything, whole new zone update follow"<<endl
;
341 for(const auto& rr
: remove
) {
342 report
<<'-'<< (rr
.d_name
+zone
) <<" IN "<<DNSRecordContent::NumberToType(rr
.d_type
)<<" "<<rr
.d_content
->getZoneRepresentation()<<endl
;
343 auto range
= records
.equal_range(tie(rr
.d_name
, rr
.d_type
, rr
.d_class
, rr
.d_content
));
344 if(range
.first
== range
.second
) {
345 cout
<<endl
<<" !! Could not find record "<<rr
.d_name
<<" to remove!!"<<endl
;
349 records
.erase(range
.first
, range
.second
);
352 for(const auto& rr
: add
) {
353 report
<<'+'<< (rr
.d_name
+zone
) <<" IN "<<DNSRecordContent::NumberToType(rr
.d_type
)<<" "<<rr
.d_content
->getZoneRepresentation()<<endl
;
357 cerr
<<"Had error condition, stopping.."<<endl
;
362 cout
<<"Writing zone to disk.. "; cout
.flush();
363 writeZoneToDisk(records
, zone
, directory
);
367 catch(PDNSException
&e2
) {
368 cerr
<<"Fatal: "<<e2
.reason
<<endl
;
370 catch(std::exception
&e
)
372 cerr
<<"Fatal: "<<e
.what()<<endl
;
376 cerr
<<"Any other exception"<<endl
;