]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ixplore.cc
Merge pull request #4611 from rgacogne/dnsdist-tcp-rfc7766-section10
[thirdparty/pdns.git] / pdns / ixplore.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 "arguments.hh"
26 #include "base64.hh"
27 #include <sys/types.h>
28 #include <dirent.h>
29
30 #include "dnsparser.hh"
31 #include "sstuff.hh"
32 #include "misc.hh"
33 #include "dnswriter.hh"
34 #include "dnsrecords.hh"
35 #include "statbag.hh"
36 #include "base32.hh"
37 #include "dnssecinfra.hh"
38
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"
44 #include <fstream>
45 #include "ixfr.hh"
46 using namespace boost::multi_index;
47 StatBag S;
48
49 ArgvMap &arg()
50 {
51 static ArgvMap theArg;
52 return theArg;
53 }
54
55
56 struct CIContentCompareStruct
57 {
58 bool operator()(const shared_ptr<DNSRecordContent>&a, const shared_ptr<DNSRecordContent>& b) const
59 {
60 return toLower(a->getZoneRepresentation()) < toLower(b->getZoneRepresentation());
61 }
62 };
63
64
65 typedef multi_index_container<
66 DNSRecord,
67 indexed_by<
68 ordered_non_unique<
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 >
75
76 >
77 >
78 >records_t;
79
80 uint32_t getSerialFromMaster(const ComboAddress& master, const DNSName& zone, shared_ptr<SOARecordContent>& sr, const TSIGTriplet& tt = TSIGTriplet())
81 {
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);
88 trc.d_fudge = 300;
89 trc.d_origID=ntohs(pw.getHeader()->id);
90 trc.d_eRcode=0;
91 addTSIG(pw, &trc, tt.name, tt.secret, "", false);
92 }
93
94 Socket s(master.sin4.sin_family, SOCK_DGRAM);
95 s.connect(master);
96 string msg((const char*)&packet[0], packet.size());
97 s.writen(msg);
98
99 string reply;
100 s.read(reply);
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));
104 }
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;
109 }
110 }
111 return 0;
112 }
113
114
115 uint32_t getSerialsFromDir(const std::string& dir)
116 {
117 uint32_t ret=0;
118 DIR* dirhdl=opendir(dir.c_str());
119 if(!dirhdl)
120 throw runtime_error("Could not open IXFR directory");
121 struct dirent *entry;
122
123 while((entry = readdir(dirhdl))) {
124 uint32_t num = atoi(entry->d_name);
125 if(std::to_string(num) == entry->d_name)
126 ret = max(num, ret);
127 }
128 closedir(dirhdl);
129 return ret;
130 }
131
132 uint32_t getSerialFromRecords(const records_t& records, DNSRecord& soaret)
133 {
134 DNSName root(".");
135 uint16_t t=QType::SOA;
136
137 auto found = records.equal_range(tie(root, t));
138
139 for(auto iter = found.first; iter != found.second; ++iter) {
140 auto soa = std::dynamic_pointer_cast<SOARecordContent>(iter->d_content);
141 soaret = *iter;
142 return soa->d_st.serial;
143 }
144 return 0;
145 }
146
147 void writeZoneToDisk(const records_t& records, const DNSName& zone, const std::string& directory)
148 {
149 DNSRecord soa;
150 int serial = getSerialFromRecords(records, soa);
151 string fname=directory +"/"+std::to_string(serial);
152 FILE* fp=fopen((fname+".partial").c_str(), "w");
153 if(!fp)
154 throw runtime_error("Unable to open file '"+fname+".partial' for writing: "+string(strerror(errno)));
155
156 records_t soarecord;
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());
165 }
166 }
167 fclose(fp);
168 rename( (fname+".partial").c_str(), fname.c_str());
169 }
170
171 void loadZoneFromDisk(records_t& records, const string& fname, const DNSName& zone)
172 {
173 ZoneParserTNG zpt(fname, zone);
174
175 DNSResourceRecord rr;
176 bool seenSOA=false;
177 unsigned int nrecords=0;
178 while(zpt.get(rr)) {
179 ++nrecords;
180 if(rr.qtype.getCode() == QType::CNAME && rr.content.empty())
181 rr.content=".";
182 rr.qname = rr.qname.makeRelative(zone);
183
184 if(rr.qtype.getCode() != QType::SOA || seenSOA==false)
185 records.insert(DNSRecord(rr));
186 if(rr.qtype.getCode() == QType::SOA) {
187 seenSOA=true;
188 }
189 }
190 cout<<"Parsed "<<nrecords<<" records"<<endl;
191 if(rr.qtype.getCode() == QType::SOA && seenSOA) {
192 cout<<"Zone was complete (SOA at end)"<<endl;
193 }
194 else {
195 records.clear();
196 throw runtime_error("Zone not complete!");
197 }
198 }
199
200 void usage() {
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;
203 }
204
205 int main(int argc, char** argv)
206 try
207 {
208 for(int n=1 ; n < argc; ++n) {
209 if ((string) argv[n] == "--help") {
210 usage();
211 return EXIT_SUCCESS;
212 }
213
214 if ((string) argv[n] == "--version") {
215 cerr<<"ixplore "<<VERSION<<endl;
216 return EXIT_SUCCESS;
217 }
218 }
219
220 reportAllTypes();
221 string command;
222 if(argc < 5 || (command=argv[1], (command!="diff" && command !="track"))) {
223 usage();
224 exit(EXIT_FAILURE);
225 }
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);
233
234 vector<DNSRecord> diff;
235
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;
239 }
240 diff.clear();
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;
244 }
245 exit(1);
246 }
247
248 // must be "track" then
249
250 /* goal in life:
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
253
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.
256
257 Next up, loop this every REFRESH seconds */
258 dns_random_init("0123456789abcdef");
259
260 DNSName zone(argv[4]);
261 ComboAddress master(argv[2], atoi(argv[3]));
262 string directory(argv[5]);
263 records_t records;
264
265 uint32_t ourSerial = getSerialsFromDir(directory);
266
267 cout<<"Loading zone, our highest available serial is "<< ourSerial<<endl;
268
269 TSIGTriplet tt;
270 if(argc > 6)
271 tt.name=DNSName(toLower(argv[6]));
272 if(argc > 7)
273 tt.algo=DNSName(toLower(argv[7]));
274
275 if(argc > 8) {
276 if(B64Decode(argv[8], tt.secret) < 0) {
277 cerr<<"Could not decode tsig secret!"<<endl;
278 exit(EXIT_FAILURE);
279 }
280 }
281
282 try {
283 if(!ourSerial)
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);
288 }
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;
295 Resolver::res_t nop;
296 vector<DNSRecord> chunk;
297 char wheel[]="|/-\\";
298 int count=0;
299 time_t last=0;
300 while(axfr.getChunk(nop, &chunk)) {
301 for(auto& dr : chunk) {
302 if(dr.d_type == QType::TSIG)
303 continue;
304 dr.d_name.makeUsRelative(zone);
305 records.insert(dr);
306 nrecords++;
307 }
308
309 if(last != time(0)) {
310 cout << '\r' << wheel[count % (sizeof(wheel)-1)] << ' ' <<nrecords;
311 count++;
312 cout.flush();
313 last=time(0);
314 }
315 }
316 cout <<"\rDone, got "<<nrecords<<" "<<endl;
317 cout<<"Writing to disk.."<<endl;
318 writeZoneToDisk(records, zone, directory);
319 }
320
321 for(;;) {
322 DNSRecord ourSoa;
323 ourSerial = getSerialFromRecords(records, ourSoa);
324
325 cout<<"Checking for update, our serial number is "<<ourSerial<<".. ";
326 cout.flush();
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);
332 continue;
333 }
334
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;
338
339 for(const auto& delta : deltas) {
340
341 const auto& remove = delta.first;
342 const auto& add = delta.second;
343
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;
349 }
350 }
351
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));
354 if(remove.empty()) {
355 cout<<"This delta is a whole new zone"<<endl;
356 report<<"- everything, whole new zone update follow"<<endl;
357 records.clear();
358 }
359
360 bool stop=false;
361
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;
367 // stop=true;
368 report.flush();
369 }
370 records.erase(range.first, range.second);
371 }
372
373 for(const auto& rr : add) {
374 report<<'+'<< (rr.d_name+zone) <<" IN "<<DNSRecordContent::NumberToType(rr.d_type)<<" "<<rr.d_content->getZoneRepresentation()<<endl;
375 records.insert(rr);
376 }
377 if(stop) {
378 cerr<<"Had error condition, stopping.."<<endl;
379 report.flush();
380 exit(1);
381 }
382 }
383 cout<<"Writing zone to disk.. "; cout.flush();
384 writeZoneToDisk(records, zone, directory);
385 cout<<"Done"<<endl;
386 }
387 }
388 catch(PDNSException &e2) {
389 cerr<<"Fatal: "<<e2.reason<<endl;
390 }
391 catch(std::exception &e)
392 {
393 cerr<<"Fatal: "<<e.what()<<endl;
394 }
395 catch(...)
396 {
397 cerr<<"Any other exception"<<endl;
398 }