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