]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsscope.cc
rec: Don't account chained queries more than once
[thirdparty/pdns.git] / pdns / dnsscope.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 #define __FAVOR_BSD
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 #include "statbag.hh"
27 #include "dnspcap.hh"
28 #include "dnsparser.hh"
29 #include "dnsname.hh"
30 #include <boost/tuple/tuple.hpp>
31 #include <boost/tuple/tuple_comparison.hpp>
32 #include <map>
33 #include <set>
34 #include <fstream>
35 #include <algorithm>
36 #include "anadns.hh"
37 #include <boost/program_options.hpp>
38
39 #include <boost/logic/tribool.hpp>
40 #include "arguments.hh"
41 #include "namespaces.hh"
42 #include <deque>
43 #include "dnsrecords.hh"
44 #include "statnode.hh"
45
46 namespace po = boost::program_options;
47 po::variables_map g_vm;
48
49 ArgvMap& arg()
50 {
51 static ArgvMap theArg;
52 return theArg;
53 }
54 StatBag S;
55
56
57
58 struct QuestionData
59 {
60 QuestionData() : d_qcount(0), d_answercount(0)
61 {
62 d_firstquestiontime.tv_sec=0;
63 }
64
65 int d_qcount;
66 int d_answercount;
67
68 struct pdns_timeval d_firstquestiontime;
69 };
70
71 typedef map<QuestionIdentifier, QuestionData> statmap_t;
72 statmap_t statmap;
73
74 unsigned int liveQuestions()
75 {
76 unsigned int ret=0;
77 for(statmap_t::value_type& val : statmap) {
78 if(!val.second.d_answercount)
79 ret++;
80 // if(val.second.d_qcount > val.second.d_answercount)
81 // ret+= val.second.d_qcount - val.second.d_answercount;
82 }
83 return ret;
84 }
85
86 struct LiveCounts
87 {
88 unsigned int questions;
89 unsigned int answers;
90 unsigned int outstanding;
91
92 LiveCounts()
93 {
94 questions=answers=outstanding=0;
95 }
96
97 LiveCounts operator-(const LiveCounts& rhs)
98 {
99 LiveCounts ret;
100 ret.questions = questions - rhs.questions;
101 ret.answers = answers - rhs.answers;
102 ret.outstanding = outstanding;
103 return ret;
104 }
105 };
106
107 void visitor(const StatNode* node, const StatNode::Stat& selfstat, const StatNode::Stat& childstat)
108 {
109 // 20% servfails, >100 children, on average less than 2 copies of a query
110 // >100 different subqueries
111 double dups=1.0*childstat.queries/node->children.size();
112 if(dups > 2.0)
113 return;
114 if(1.0*childstat.servfails / childstat.queries > 0.2 && node->children.size()>100) {
115 cout<<node->fullname<<", servfails: "<<childstat.servfails<<", nxdomains: "<<childstat.nxdomains<<", remotes: "<<childstat.remotes.size()<<", children: "<<node->children.size()<<", childstat.queries: "<<childstat.queries;
116 cout<<", dups2: "<<dups<<endl;
117 for(const StatNode::Stat::remotes_t::value_type& rem : childstat.remotes) {
118 cout<<"source: "<<node->fullname<<"\t"<<rem.first.toString()<<"\t"<<rem.second<<endl;
119 }
120 }
121 }
122
123 int main(int argc, char** argv)
124 try
125 {
126 po::options_description desc("Allowed options"), hidden, alloptions;
127 desc.add_options()
128 ("help,h", "produce help message")
129 ("version", "print version number")
130 ("rd", po::value<bool>(), "If set to true, only process RD packets, to false only non-RD, unset: both")
131 ("ipv4", po::value<bool>()->default_value(true), "Process IPv4 packets")
132 ("ipv6", po::value<bool>()->default_value(true), "Process IPv6 packets")
133 ("servfail-tree", "Figure out subtrees that generate servfails")
134 ("load-stats,l", po::value<string>()->default_value(""), "if set, emit per-second load statistics (questions, answers, outstanding)")
135 ("write-failures,w", po::value<string>()->default_value(""), "if set, write weird packets to this PCAP file")
136 ("verbose,v", "be verbose");
137
138 hidden.add_options()
139 ("files", po::value<vector<string> >(), "files");
140
141 alloptions.add(desc).add(hidden);
142
143 po::positional_options_description p;
144 p.add("files", -1);
145
146 po::store(po::command_line_parser(argc, argv).options(alloptions).positional(p).run(), g_vm);
147 po::notify(g_vm);
148
149 vector<string> files;
150 if(g_vm.count("files"))
151 files = g_vm["files"].as<vector<string> >();
152
153 if(g_vm.count("version")) {
154 cerr<<"dnsscope "<<VERSION<<endl;
155 exit(0);
156 }
157
158 if(files.empty() || g_vm.count("help")) {
159 cerr<<"Syntax: dnsscope filename.pcap"<<endl;
160 cout << desc << endl;
161 exit(0);
162 }
163
164 StatNode root;
165
166 bool verbose = g_vm.count("verbose");
167
168 bool haveRDFilter=0, rdFilter=0;
169 if(g_vm.count("rd")) {
170 rdFilter = g_vm["rd"].as<bool>();
171 haveRDFilter=1;
172 cout<<"Filtering on recursion desired="<<rdFilter<<endl;
173 }
174 else
175 cout<<"Warning, looking at both RD and non-RD traffic!"<<endl;
176
177 bool doIPv4 = g_vm["ipv4"].as<bool>();
178 bool doIPv6 = g_vm["ipv6"].as<bool>();
179 bool doServFailTree = g_vm.count("servfail-tree");
180 int dnserrors=0, bogus=0;
181 typedef map<uint32_t,uint32_t> cumul_t;
182 cumul_t cumul;
183 unsigned int untracked=0, errorresult=0, reallylate=0, nonRDQueries=0, queries=0;
184 unsigned int ipv4DNSPackets=0, ipv6DNSPackets=0, fragmented=0, rdNonRAAnswers=0;
185 unsigned int answers=0, nonDNSIP=0, rdFilterMismatch=0;
186 unsigned int dnssecOK=0, edns=0;
187 unsigned int dnssecCD=0, dnssecAD=0;
188 typedef map<uint16_t,uint32_t> rcodes_t;
189 rcodes_t rcodes;
190
191 time_t lowestTime=2000000000, highestTime=0;
192 time_t lastsec=0;
193 LiveCounts lastcounts;
194 set<ComboAddress, ComboAddress::addressOnlyLessThan> requestors, recipients, rdnonra;
195 typedef vector<pair<time_t, LiveCounts> > pcounts_t;
196 pcounts_t pcounts;
197 OPTRecordContent::report();
198 for(unsigned int fno=0; fno < files.size(); ++fno) {
199 PcapPacketReader pr(files[fno]);
200 PcapPacketWriter* pw=0;
201 if(!g_vm["write-failures"].as<string>().empty())
202 pw=new PcapPacketWriter(g_vm["write-failures"].as<string>(), pr);
203
204 EDNSOpts edo;
205 while(pr.getUDPPacket()) {
206
207 if((ntohs(pr.d_udp->uh_dport)==5300 || ntohs(pr.d_udp->uh_sport)==5300 ||
208 ntohs(pr.d_udp->uh_dport)==53 || ntohs(pr.d_udp->uh_sport)==53) &&
209 pr.d_len > 12) {
210 try {
211 if((pr.d_ip->ip_v == 4 && !doIPv4) || (pr.d_ip->ip_v == 6 && !doIPv6))
212 continue;
213 if(pr.d_ip->ip_v == 4) {
214 uint16_t frag = ntohs(pr.d_ip->ip_off);
215 if((frag & IP_MF) || (frag & IP_OFFMASK)) { // more fragments or IS a fragment
216 fragmented++;
217 continue;
218 }
219 }
220 MOADNSParser mdp(false, (const char*)pr.d_payload, pr.d_len);
221 if(haveRDFilter && mdp.d_header.rd != rdFilter) {
222 rdFilterMismatch++;
223 continue;
224 }
225
226 if(!mdp.d_header.qr && getEDNSOpts(mdp, &edo)) {
227 edns++;
228 if(edo.d_Z & EDNSOpts::DNSSECOK)
229 dnssecOK++;
230 if(mdp.d_header.cd)
231 dnssecCD++;
232 if(mdp.d_header.ad)
233 dnssecAD++;
234 }
235
236
237 if(pr.d_ip->ip_v == 4)
238 ++ipv4DNSPackets;
239 else
240 ++ipv6DNSPackets;
241
242 if(pr.d_pheader.ts.tv_sec != lastsec) {
243 LiveCounts lc;
244 if(lastsec) {
245 lc.questions = queries;
246 lc.answers = answers;
247 lc.outstanding = liveQuestions();
248
249 LiveCounts diff = lc - lastcounts;
250 pcounts.push_back(make_pair(pr.d_pheader.ts.tv_sec, diff));
251
252 }
253 lastsec = pr.d_pheader.ts.tv_sec;
254 lastcounts = lc;
255 }
256
257 lowestTime=min((time_t)lowestTime, (time_t)pr.d_pheader.ts.tv_sec);
258 highestTime=max((time_t)highestTime, (time_t)pr.d_pheader.ts.tv_sec);
259
260 string name=mdp.d_qname.toString()+"|"+DNSRecordContent::NumberToType(mdp.d_qtype);
261
262 QuestionIdentifier qi=QuestionIdentifier::create(pr.getSource(), pr.getDest(), mdp);
263
264 if(!mdp.d_header.qr) { // question
265 if(!mdp.d_header.rd)
266 nonRDQueries++;
267 queries++;
268
269 ComboAddress rem = pr.getSource();
270 rem.sin4.sin_port=0;
271 requestors.insert(rem);
272
273 QuestionData& qd=statmap[qi];
274
275 if(!qd.d_firstquestiontime.tv_sec)
276 qd.d_firstquestiontime=pr.d_pheader.ts;
277 qd.d_qcount++;
278 }
279 else { // answer
280 rcodes[mdp.d_header.rcode]++;
281 answers++;
282 if(mdp.d_header.rd && !mdp.d_header.ra) {
283 rdNonRAAnswers++;
284 rdnonra.insert(pr.getDest());
285 }
286
287 if(mdp.d_header.ra) {
288 ComboAddress rem = pr.getDest();
289 rem.sin4.sin_port=0;
290 recipients.insert(rem);
291 }
292
293 QuestionData& qd=statmap[qi];
294
295 if(!qd.d_qcount)
296 untracked++;
297
298 qd.d_answercount++;
299
300 if(qd.d_qcount) {
301 uint32_t usecs= (pr.d_pheader.ts.tv_sec - qd.d_firstquestiontime.tv_sec) * 1000000 +
302 (pr.d_pheader.ts.tv_usec - qd.d_firstquestiontime.tv_usec) ;
303 // cout<<"Took: "<<usecs<<"usec\n";
304 if(usecs<2049000)
305 cumul[usecs]++;
306 else
307 reallylate++;
308
309 if(mdp.d_header.rcode != 0 && mdp.d_header.rcode!=3)
310 errorresult++;
311 ComboAddress rem = pr.getDest();
312 rem.sin4.sin_port=0;
313
314 if(doServFailTree)
315 root.submit(mdp.d_qname, mdp.d_header.rcode, rem);
316 }
317
318 if(!qd.d_qcount || qd.d_qcount == qd.d_answercount)
319 statmap.erase(qi);
320 }
321
322
323 }
324 catch(MOADNSException& mde) {
325 if(verbose)
326 cout<<"error parsing packet: "<<mde.what()<<endl;
327 if(pw)
328 pw->write();
329 dnserrors++;
330 continue;
331 }
332 catch(std::exception& e) {
333 if(verbose)
334 cout<<"error parsing packet: "<<e.what()<<endl;
335
336 if(pw)
337 pw->write();
338 bogus++;
339 continue;
340 }
341 }
342 else { // non-DNS ip
343 nonDNSIP++;
344 }
345 }
346 cout<<"PCAP contained "<<pr.d_correctpackets<<" correct packets, "<<pr.d_runts<<" runts, "<< pr.d_oversized<<" oversize, "<<pr.d_nonetheripudp<<" non-UDP.\n";
347
348 }
349 cout<<"Timespan: "<<(highestTime-lowestTime)/3600.0<<" hours"<<endl;
350
351 cout<<nonDNSIP<<" non-DNS UDP, "<<dnserrors<<" dns decoding errors, "<<bogus<<" bogus packets"<<endl;
352 cout<<"Ignored fragment packets: "<<fragmented<<endl;
353 cout<<"Dropped DNS packets based on recursion-desired filter: "<<rdFilterMismatch<<endl;
354 cout<<"DNS IPv4: "<<ipv4DNSPackets<<" packets, IPv6: "<<ipv6DNSPackets<<" packets"<<endl;
355 cout<<"Questions: "<<queries<<", answers: "<<answers<<endl;
356 unsigned int unanswered=0;
357
358
359 // ofstream openf("openf");
360 for(statmap_t::const_iterator i=statmap.begin(); i!=statmap.end(); ++i) {
361 if(!i->second.d_answercount) {
362 unanswered++;
363 }
364 //openf<< i->first.d_source.toStringWithPort()<<' ' <<i->first.d_dest.toStringWithPort()<<' '<<i->first.d_id<<' '<<i->first.d_qname <<" " <<i->first.d_qtype<< " "<<i->second.d_qcount <<" " <<i->second.d_answercount<<endl;
365 }
366
367 cout<< boost::format("%d (%.02f%% of all) queries did not request recursion") % nonRDQueries % ((nonRDQueries*100.0)/queries) << endl;
368 cout<< rdNonRAAnswers << " answers had recursion desired bit set, but recursion available=0 (for "<<rdnonra.size()<<" remotes)"<<endl;
369 cout<<statmap.size()<<" queries went unanswered, of which "<< statmap.size()-unanswered<<" were answered on exact retransmit"<<endl;
370 cout<<untracked<<" responses could not be matched to questions"<<endl;
371 cout<<edns <<" questions requested EDNS processing, do=1: "<<dnssecOK<<", ad=1: "<<dnssecAD<<", cd=1: "<<dnssecCD<<endl;
372
373 if(answers) {
374 cout<<(boost::format("%1% %|25t|%2%") % "Rcode" % "Count\n");
375 for(rcodes_t::const_iterator i=rcodes.begin(); i!=rcodes.end(); ++i)
376 cout<<(boost::format("%s %|25t|%d %|35t|(%.1f%%)") % RCode::to_s(i->first) % i->second % (i->second*100.0/answers))<<endl;
377 }
378
379 uint32_t sum=0;
380 // ofstream stats("stats");
381 uint32_t totpackets=reallylate;
382 double tottime=0;
383 for(cumul_t::const_iterator i=cumul.begin(); i!=cumul.end(); ++i) {
384 // stats<<i->first<<"\t"<<(sum+=i->second)<<"\n";
385 totpackets+=i->second;
386 tottime+=i->first*i->second;
387 }
388
389 typedef map<uint32_t, bool> done_t;
390 done_t done;
391 done[50];
392 done[100];
393 done[200];
394 done[300];
395 done[400];
396 done[800];
397 done[1000];
398 done[2000];
399 done[4000];
400 done[8000];
401 done[32000];
402 done[64000];
403 done[256000];
404 done[1024000];
405 done[2048000];
406
407 cout.setf(std::ios::fixed);
408 cout.precision(2);
409 sum=0;
410
411 double lastperc=0, perc=0;
412 for(cumul_t::const_iterator i=cumul.begin(); i!=cumul.end(); ++i) {
413 sum+=i->second;
414
415 for(done_t::iterator j=done.begin(); j!=done.end(); ++j)
416 if(!j->second && i->first > j->first) {
417 j->second=true;
418
419 perc=sum*100.0/totpackets;
420 if(j->first < 1024)
421 cout<< perc <<"% of questions answered within " << j->first << " usec (";
422 else
423 cout<< perc <<"% of questions answered within " << j->first/1000.0 << " msec (";
424
425 cout<<perc-lastperc<<"%)\n";
426 lastperc=sum*100.0/totpackets;
427 }
428 }
429 cout<<reallylate<<" responses ("<<reallylate*100.0/answers<<"%) older than 2 seconds"<<endl;
430 if(totpackets)
431 cout<<"Average non-late response time: "<<tottime/totpackets<<" usec"<<endl;
432
433 if(!g_vm["load-stats"].as<string>().empty()) {
434 ofstream load(g_vm["load-stats"].as<string>().c_str());
435 if(!load)
436 throw runtime_error("Error writing load statistics to "+g_vm["load-stats"].as<string>());
437 for(pcounts_t::value_type& val : pcounts) {
438 load<<val.first<<'\t'<<val.second.questions<<'\t'<<val.second.answers<<'\t'<<val.second.outstanding<<'\n';
439 }
440 }
441
442
443 cout<<"Saw questions from "<<requestors.size()<<" distinct remotes, answers to "<<recipients.size()<<endl;
444 ofstream remotes("remotes");
445 for(const ComboAddress& rem : requestors) {
446 remotes<<rem.toString()<<'\n';
447 }
448
449 vector<ComboAddress> diff;
450 set_difference(requestors.begin(), requestors.end(), recipients.begin(), recipients.end(), back_inserter(diff), ComboAddress::addressOnlyLessThan());
451 cout<<"Saw "<<diff.size()<<" unique remotes asking questions, but not getting RA answers"<<endl;
452
453 ofstream ignored("ignored");
454 for(const ComboAddress& rem : diff) {
455 ignored<<rem.toString()<<'\n';
456 }
457 ofstream rdnonrafs("rdnonra");
458 for(const ComboAddress& rem : rdnonra) {
459 rdnonrafs<<rem.toString()<<'\n';
460 }
461
462 if(doServFailTree) {
463 StatNode::Stat node;
464 root.visit(visitor, node);
465 }
466
467 }
468 catch(std::exception& e)
469 {
470 cerr<<"Fatal: "<<e.what()<<endl;
471 }