]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/dnsscope.cc
Merge pull request #5461 from rgacogne/rec-cache-index
[thirdparty/pdns.git] / pdns / dnsscope.cc
CommitLineData
12471842
PL
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 */
da327a1b 22#define __FAVOR_BSD
870a0fe4
AT
23#ifdef HAVE_CONFIG_H
24#include "config.h"
25#endif
a640a9d4
BH
26#include "statbag.hh"
27#include "dnspcap.hh"
28#include "dnsparser.hh"
68ca579c 29#include "dnsname.hh"
a640a9d4
BH
30#include <boost/tuple/tuple.hpp>
31#include <boost/tuple/tuple_comparison.hpp>
32#include <map>
33#include <set>
34#include <fstream>
da327a1b 35#include <algorithm>
9a450843 36#include "anadns.hh"
611a846e 37#include <boost/program_options.hpp>
fa8fd4d2 38
611a846e 39#include <boost/logic/tribool.hpp>
ce9309b7 40#include "arguments.hh"
10f4eea8 41#include "namespaces.hh"
9ad511a7 42#include <deque>
168eb01c 43#include "dnsrecords.hh"
6264512b 44#include "statnode.hh"
a640a9d4 45
611a846e 46namespace po = boost::program_options;
47po::variables_map g_vm;
a640a9d4 48
ce9309b7 49ArgvMap& arg()
50{
51 static ArgvMap theArg;
52 return theArg;
53}
611a846e 54StatBag S;
a640a9d4 55
9ad511a7 56
57
a640a9d4
BH
58struct 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
f79a7a88 68 struct pdns_timeval d_firstquestiontime;
a640a9d4
BH
69};
70
9a450843 71typedef map<QuestionIdentifier, QuestionData> statmap_t;
a640a9d4
BH
72statmap_t statmap;
73
ad79b1b5 74unsigned int liveQuestions()
75{
76 unsigned int ret=0;
ef7cd021 77 for(statmap_t::value_type& val : statmap) {
ad79b1b5 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
86struct 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
6264512b 107void 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}
ce9309b7 122
a640a9d4
BH
123int main(int argc, char** argv)
124try
125{
611a846e 126 po::options_description desc("Allowed options"), hidden, alloptions;
127 desc.add_options()
128 ("help,h", "produce help message")
0670347d 129 ("version", "print version number")
611a846e 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")
9ad511a7 133 ("servfail-tree", "Figure out subtrees that generate servfails")
9a07e342 134 ("load-stats,l", po::value<string>()->default_value(""), "if set, emit per-second load statistics (questions, answers, outstanding)")
be427910 135 ("write-failures,w", po::value<string>()->default_value(""), "if set, write weird packets to this PCAP file")
611a846e 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);
be427910 148
149 vector<string> files;
150 if(g_vm.count("files"))
151 files = g_vm["files"].as<vector<string> >();
0670347d
PL
152
153 if(g_vm.count("version")) {
154 cerr<<"dnsscope "<<VERSION<<endl;
155 exit(0);
156 }
157
82b4a662 158 if(files.empty() || g_vm.count("help")) {
1cf46d65 159 cerr<<"Syntax: dnsscope filename.pcap"<<endl;
611a846e 160 cout << desc << endl;
0670347d 161 exit(0);
1cf46d65 162 }
a640a9d4 163
9ad511a7 164 StatNode root;
165
be427910 166 bool verbose = g_vm.count("verbose");
167
611a846e 168 bool haveRDFilter=0, rdFilter=0;
169 if(g_vm.count("rd")) {
170 rdFilter = g_vm["rd"].as<bool>();
171 haveRDFilter=1;
ad79b1b5 172 cout<<"Filtering on recursion desired="<<rdFilter<<endl;
611a846e 173 }
ad79b1b5 174 else
175 cout<<"Warning, looking at both RD and non-RD traffic!"<<endl;
611a846e 176
177 bool doIPv4 = g_vm["ipv4"].as<bool>();
178 bool doIPv6 = g_vm["ipv6"].as<bool>();
9ad511a7 179 bool doServFailTree = g_vm.count("servfail-tree");
da327a1b 180 int dnserrors=0, bogus=0;
a640a9d4
BH
181 typedef map<uint32_t,uint32_t> cumul_t;
182 cumul_t cumul;
54067f8c 183 unsigned int untracked=0, errorresult=0, reallylate=0, nonRDQueries=0, queries=0;
d2d8cafd 184 unsigned int ipv4DNSPackets=0, ipv6DNSPackets=0, fragmented=0, rdNonRAAnswers=0;
185 unsigned int answers=0, nonDNSIP=0, rdFilterMismatch=0;
168eb01c 186 unsigned int dnssecOK=0, edns=0;
187 unsigned int dnssecCD=0, dnssecAD=0;
a640a9d4
BH
188 typedef map<uint16_t,uint32_t> rcodes_t;
189 rcodes_t rcodes;
82b4a662 190
1aac6e17 191 time_t lowestTime=2000000000, highestTime=0;
ad79b1b5 192 time_t lastsec=0;
193 LiveCounts lastcounts;
6264512b 194 set<ComboAddress, ComboAddress::addressOnlyLessThan> requestors, recipients, rdnonra;
ad79b1b5 195 typedef vector<pair<time_t, LiveCounts> > pcounts_t;
196 pcounts_t pcounts;
168eb01c 197 OPTRecordContent::report();
82b4a662 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
168eb01c 204 EDNSOpts edo;
82b4a662 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 }
27c0050c 220 MOADNSParser mdp(false, (const char*)pr.d_payload, pr.d_len);
82b4a662 221 if(haveRDFilter && mdp.d_header.rd != rdFilter) {
222 rdFilterMismatch++;
be427910 223 continue;
224 }
ad79b1b5 225
168eb01c 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
82b4a662 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;
ad79b1b5 255 }
ad79b1b5 256
82b4a662 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);
4957a608 259
68ca579c 260 string name=mdp.d_qname.toString()+"|"+DNSRecordContent::NumberToType(mdp.d_qtype);
4957a608 261
82b4a662 262 QuestionIdentifier qi=QuestionIdentifier::create(pr.getSource(), pr.getDest(), mdp);
4957a608 263
82b4a662 264 if(!mdp.d_header.qr) { // question
265 if(!mdp.d_header.rd)
266 nonRDQueries++;
267 queries++;
fa7c37fe 268
82b4a662 269 ComboAddress rem = pr.getSource();
270 rem.sin4.sin_port=0;
271 requestors.insert(rem);
ce9309b7 272
82b4a662 273 QuestionData& qd=statmap[qi];
4957a608 274
82b4a662 275 if(!qd.d_firstquestiontime.tv_sec)
276 qd.d_firstquestiontime=pr.d_pheader.ts;
277 qd.d_qcount++;
ce9309b7 278 }
82b4a662 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 }
ce9309b7 286
82b4a662 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++;
4957a608 308
82b4a662 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;
9ad511a7 313
82b4a662 314 if(doServFailTree)
315 root.submit(mdp.d_qname, mdp.d_header.rcode, rem);
316 }
4957a608 317
82b4a662 318 if(!qd.d_qcount || qd.d_qcount == qd.d_answercount)
319 statmap.erase(qi);
320 }
4957a608 321
ce9309b7 322
82b4a662 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 }
a640a9d4 341 }
82b4a662 342 else { // non-DNS ip
343 nonDNSIP++;
a640a9d4
BH
344 }
345 }
82b4a662 346 cout<<"PCAP contained "<<pr.d_correctpackets<<" correct packets, "<<pr.d_runts<<" runts, "<< pr.d_oversized<<" oversize, "<<pr.d_nonetheripudp<<" non-UDP.\n";
347
a640a9d4 348 }
a5d9353e 349 cout<<"Timespan: "<<(highestTime-lowestTime)/3600.0<<" hours"<<endl;
1aac6e17 350
d2d8cafd 351 cout<<nonDNSIP<<" non-DNS UDP, "<<dnserrors<<" dns decoding errors, "<<bogus<<" bogus packets"<<endl;
ad79b1b5 352 cout<<"Ignored fragment packets: "<<fragmented<<endl;
d2d8cafd 353 cout<<"Dropped DNS packets based on recursion-desired filter: "<<rdFilterMismatch<<endl;
354 cout<<"DNS IPv4: "<<ipv4DNSPackets<<" packets, IPv6: "<<ipv6DNSPackets<<" packets"<<endl;
ce9309b7 355 cout<<"Questions: "<<queries<<", answers: "<<answers<<endl;
a640a9d4 356 unsigned int unanswered=0;
ad79b1b5 357
358
359 // ofstream openf("openf");
a640a9d4 360 for(statmap_t::const_iterator i=statmap.begin(); i!=statmap.end(); ++i) {
7aba88c0 361 if(!i->second.d_answercount) {
a640a9d4 362 unanswered++;
7aba88c0 363 }
ad79b1b5 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;
a640a9d4
BH
365 }
366
a5d9353e 367 cout<< boost::format("%d (%.02f%% of all) queries did not request recursion") % nonRDQueries % ((nonRDQueries*100.0)/queries) << endl;
012de544 368 cout<< rdNonRAAnswers << " answers had recursion desired bit set, but recursion available=0 (for "<<rdnonra.size()<<" remotes)"<<endl;
a5d9353e 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;
168eb01c 371 cout<<edns <<" questions requested EDNS processing, do=1: "<<dnssecOK<<", ad=1: "<<dnssecAD<<", cd=1: "<<dnssecCD<<endl;
ce9309b7 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 }
a640a9d4
BH
378
379 uint32_t sum=0;
1ffb2c7f 380 // ofstream stats("stats");
012de544 381 uint32_t totpackets=reallylate;
a640a9d4
BH
382 double tottime=0;
383 for(cumul_t::const_iterator i=cumul.begin(); i!=cumul.end(); ++i) {
1ffb2c7f 384 // stats<<i->first<<"\t"<<(sum+=i->second)<<"\n";
a640a9d4
BH
385 totpackets+=i->second;
386 tottime+=i->first*i->second;
387 }
012de544 388
a640a9d4
BH
389 typedef map<uint32_t, bool> done_t;
390 done_t done;
391 done[50];
392 done[100];
393 done[200];
a640a9d4 394 done[300];
a640a9d4
BH
395 done[400];
396 done[800];
397 done[1000];
398 done[2000];
399 done[4000];
400 done[8000];
a640a9d4
BH
401 done[32000];
402 done[64000];
a640a9d4 403 done[256000];
a640a9d4
BH
404 done[1024000];
405 done[2048000];
406
843c7356 407 cout.setf(std::ios::fixed);
a640a9d4
BH
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)
1aac6e17 416 if(!j->second && i->first > j->first) {
4957a608
BH
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;
1aac6e17 427 }
a640a9d4 428 }
012de544 429 cout<<reallylate<<" responses ("<<reallylate*100.0/answers<<"%) older than 2 seconds"<<endl;
a640a9d4 430 if(totpackets)
012de544 431 cout<<"Average non-late response time: "<<tottime/totpackets<<" usec"<<endl;
9a07e342 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>());
ef7cd021 437 for(pcounts_t::value_type& val : pcounts) {
9a07e342 438 load<<val.first<<'\t'<<val.second.questions<<'\t'<<val.second.answers<<'\t'<<val.second.outstanding<<'\n';
439 }
440 }
ce9309b7 441
442
443 cout<<"Saw questions from "<<requestors.size()<<" distinct remotes, answers to "<<recipients.size()<<endl;
444 ofstream remotes("remotes");
ef7cd021 445 for(const ComboAddress& rem : requestors) {
ce9309b7 446 remotes<<rem.toString()<<'\n';
447 }
448
449 vector<ComboAddress> diff;
6264512b 450 set_difference(requestors.begin(), requestors.end(), recipients.begin(), recipients.end(), back_inserter(diff), ComboAddress::addressOnlyLessThan());
012de544 451 cout<<"Saw "<<diff.size()<<" unique remotes asking questions, but not getting RA answers"<<endl;
ce9309b7 452
453 ofstream ignored("ignored");
ef7cd021 454 for(const ComboAddress& rem : diff) {
ce9309b7 455 ignored<<rem.toString()<<'\n';
456 }
457 ofstream rdnonrafs("rdnonra");
ef7cd021 458 for(const ComboAddress& rem : rdnonra) {
ce9309b7 459 rdnonrafs<<rem.toString()<<'\n';
460 }
9ad511a7 461
462 if(doServFailTree) {
463 StatNode::Stat node;
464 root.visit(visitor, node);
465 }
466
a640a9d4 467}
5172cb78 468catch(std::exception& e)
a640a9d4
BH
469{
470 cerr<<"Fatal: "<<e.what()<<endl;
471}