2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
32 #include "dnsparser.hh"
34 #include <boost/tuple/tuple.hpp>
35 #include <boost/tuple/tuple_comparison.hpp>
41 #include <boost/program_options.hpp>
42 #include <unordered_set>
43 #include <boost/logic/tribool.hpp>
44 #include "arguments.hh"
45 #include "namespaces.hh"
46 #include "dnsrecords.hh"
47 #include "statnode.hh"
49 namespace po
= boost::program_options
;
50 po::variables_map g_vm
;
54 static ArgvMap theArg
;
63 QuestionData() : d_qcount(0), d_answercount(0)
65 d_firstquestiontime
.tv_sec
=0;
71 struct pdns_timeval d_firstquestiontime
;
74 typedef map
<QuestionIdentifier
, QuestionData
> statmap_t
;
77 unsigned int liveQuestions()
80 for(statmap_t::value_type
& val
: statmap
) {
81 if(!val
.second
.d_answercount
)
83 // if(val.second.d_qcount > val.second.d_answercount)
84 // ret+= val.second.d_qcount - val.second.d_answercount;
91 unsigned int questions
;
93 unsigned int outstanding
;
97 questions
=answers
=outstanding
=0;
100 LiveCounts
operator-(const LiveCounts
& rhs
)
103 ret
.questions
= questions
- rhs
.questions
;
104 ret
.answers
= answers
- rhs
.answers
;
105 ret
.outstanding
= outstanding
;
110 void visitor(const StatNode
* node
, const StatNode::Stat
& selfstat
, const StatNode::Stat
& childstat
)
112 // 20% servfails, >100 children, on average less than 2 copies of a query
113 // >100 different subqueries
114 double dups
=1.0*childstat
.queries
/node
->children
.size();
117 if(1.0*childstat
.servfails
/ childstat
.queries
> 0.2 && node
->children
.size()>100) {
118 cout
<<node
->fullname
<<", servfails: "<<childstat
.servfails
<<", nxdomains: "<<childstat
.nxdomains
<<", remotes: "<<childstat
.remotes
.size()<<", children: "<<node
->children
.size()<<", childstat.queries: "<<childstat
.queries
;
119 cout
<<", dups2: "<<dups
<<endl
;
120 for(const StatNode::Stat::remotes_t::value_type
& rem
: childstat
.remotes
) {
121 cout
<<"source: "<<node
->fullname
<<"\t"<<rem
.first
.toString()<<"\t"<<rem
.second
<<endl
;
126 const struct timeval
operator-(const struct pdns_timeval
& lhs
, const struct pdns_timeval
& rhs
)
128 struct timeval a
{lhs
.tv_sec
, static_cast<suseconds_t
>(lhs
.tv_usec
)}, b
{rhs
.tv_sec
, static_cast<suseconds_t
>(rhs
.tv_usec
)};
129 return operator-(a
,b
);
133 int main(int argc
, char** argv
)
136 po::options_description
desc("Allowed options"), hidden
, alloptions
;
138 ("help,h", "produce help message")
139 ("version", "print version number")
140 ("rd", po::value
<bool>(), "If set to true, only process RD packets, to false only non-RD, unset: both")
141 ("ipv4", po::value
<bool>()->default_value(true), "Process IPv4 packets")
142 ("ipv6", po::value
<bool>()->default_value(true), "Process IPv6 packets")
143 #if HAVE_BOOST_GE_148
144 ("log-histogram", "Write a log-histogram to file 'log-histogram'")
145 ("full-histogram", po::value
<double>(), "Write a log-histogram to file 'full-histogram' with this millisecond bin size")
147 ("filter-name,f", po::value
<string
>(), "Do statistics only for queries within this domain")
148 ("load-stats,l", po::value
<string
>()->default_value(""), "if set, emit per-second load statistics (questions, answers, outstanding)")
149 ("no-servfail-stats", "Don't include servfails in response time stats")
150 ("servfail-tree", "Figure out subtrees that generate servfails")
151 ("stats-dir", po::value
<string
>()->default_value("."), "Directory where statistics will be saved")
152 ("write-failures,w", po::value
<string
>()->default_value(""), "if set, write weird packets to this PCAP file")
153 ("verbose,v", "be verbose");
156 ("files", po::value
<vector
<string
> >(), "files");
158 alloptions
.add(desc
).add(hidden
);
160 po::positional_options_description p
;
163 po::store(po::command_line_parser(argc
, argv
).options(alloptions
).positional(p
).run(), g_vm
);
166 vector
<string
> files
;
167 if(g_vm
.count("files"))
168 files
= g_vm
["files"].as
<vector
<string
> >();
170 if(g_vm
.count("version")) {
171 cerr
<<"dnsscope "<<VERSION
<<endl
;
175 if(files
.empty() || g_vm
.count("help")) {
176 cerr
<<"Syntax: dnsscope filename.pcap [filenam2.pcap...]"<<endl
;
177 cout
<< desc
<< endl
;
182 if(g_vm
.count("filter-name"))
183 filtername
= DNSName(g_vm
["filter-name"].as
<string
>());
184 uint32_t nameMismatch
= 0;
188 bool verbose
= g_vm
.count("verbose");
190 bool haveRDFilter
=0, rdFilter
=0;
191 if(g_vm
.count("rd")) {
192 rdFilter
= g_vm
["rd"].as
<bool>();
194 cout
<<"Filtering on recursion desired="<<rdFilter
<<endl
;
197 cout
<<"Warning, looking at both RD and non-RD traffic!"<<endl
;
199 bool doIPv4
= g_vm
["ipv4"].as
<bool>();
200 bool doIPv6
= g_vm
["ipv6"].as
<bool>();
201 bool doServFailTree
= g_vm
.count("servfail-tree");
202 bool noservfailstats
= g_vm
.count("no-servfail-stats");
203 int dnserrors
=0, parsefail
=0;
204 typedef map
<uint32_t,uint32_t> cumul_t
;
206 unsigned int untracked
=0, errorresult
=0, nonRDQueries
=0, queries
=0;
207 unsigned int ipv4DNSPackets
=0, ipv6DNSPackets
=0, fragmented
=0, rdNonRAAnswers
=0;
208 unsigned int answers
=0, nonDNSIP
=0, rdFilterMismatch
=0;
209 unsigned int dnssecOK
=0, edns
=0;
210 unsigned int dnssecCD
=0, dnssecAD
=0;
211 unsigned int reuses
=0;
212 typedef map
<uint16_t,uint32_t> rcodes_t
;
215 time_t lowestTime
=2000000000, highestTime
=0;
217 LiveCounts lastcounts
;
218 std::unordered_set
<ComboAddress
, ComboAddress::addressOnlyHash
> requestors
, recipients
, rdnonra
;
219 typedef vector
<pair
<time_t, LiveCounts
> > pcounts_t
;
221 OPTRecordContent::report();
223 for(unsigned int fno
=0; fno
< files
.size(); ++fno
) {
224 PcapPacketReader
pr(files
[fno
]);
225 PcapPacketWriter
* pw
=0;
226 if(!g_vm
["write-failures"].as
<string
>().empty())
227 pw
=new PcapPacketWriter(g_vm
["write-failures"].as
<string
>(), pr
);
230 while(pr
.getUDPPacket()) {
232 if((ntohs(pr
.d_udp
->uh_dport
)==5300 || ntohs(pr
.d_udp
->uh_sport
)==5300 ||
233 ntohs(pr
.d_udp
->uh_dport
)==53 || ntohs(pr
.d_udp
->uh_sport
)==53) &&
236 if((pr
.d_ip
->ip_v
== 4 && !doIPv4
) || (pr
.d_ip
->ip_v
== 6 && !doIPv6
))
238 if(pr
.d_ip
->ip_v
== 4) {
239 uint16_t frag
= ntohs(pr
.d_ip
->ip_off
);
240 if((frag
& IP_MF
) || (frag
& IP_OFFMASK
)) { // more fragments or IS a fragment
246 DNSName
qname((const char*)pr
.d_payload
, pr
.d_len
, 12, false, &qtype
);
247 struct dnsheader header
;
248 memcpy(&header
, (struct dnsheader
*)pr
.d_payload
, 12);
250 if(haveRDFilter
&& header
.rd
!= rdFilter
) {
255 if(!filtername
.empty() && !qname
.isPartOf(filtername
)) {
262 if(getEDNSUDPPayloadSizeAndZ((const char*)pr
.d_payload
, pr
.d_len
, &udpsize
, &z
)) {
264 if(z
& EDNSOpts::DNSSECOK
)
273 if(pr
.d_ip
->ip_v
== 4)
278 if(pr
.d_pheader
.ts
.tv_sec
!= lastsec
) {
281 lc
.questions
= queries
;
282 lc
.answers
= answers
;
283 lc
.outstanding
= liveQuestions();
285 LiveCounts diff
= lc
- lastcounts
;
286 pcounts
.push_back(make_pair(pr
.d_pheader
.ts
.tv_sec
, diff
));
289 lastsec
= pr
.d_pheader
.ts
.tv_sec
;
293 lowestTime
=min((time_t)lowestTime
, (time_t)pr
.d_pheader
.ts
.tv_sec
);
294 highestTime
=max((time_t)highestTime
, (time_t)pr
.d_pheader
.ts
.tv_sec
);
296 QuestionIdentifier qi
=QuestionIdentifier::create(pr
.getSource(), pr
.getDest(), header
, qname
, qtype
);
298 if(!header
.qr
) { // question
299 // cout<<"Query "<<qi<<endl;
304 ComboAddress rem
= pr
.getSource();
306 requestors
.insert(rem
);
308 QuestionData
& qd
=statmap
[qi
];
310 if(!qd
.d_firstquestiontime
.tv_sec
)
311 qd
.d_firstquestiontime
=pr
.d_pheader
.ts
;
313 auto delta
=makeFloat(pr
.d_pheader
.ts
- qd
.d_firstquestiontime
);
314 // cout<<"Reuse of "<<qi<<", delta t="<<delta<<", count="<<qd.d_qcount<<endl;
316 // cout<<"Resetting old entry for "<<qi<<", too old"<<endl;
319 qd
.d_firstquestiontime
=pr
.d_pheader
.ts
;
326 // cout<<"Response "<<qi<<endl;
327 rcodes
[header
.rcode
]++;
329 if(header
.rd
&& !header
.ra
) {
331 rdnonra
.insert(pr
.getDest());
335 ComboAddress rem
= pr
.getDest();
337 recipients
.insert(rem
);
340 QuestionData
& qd
=statmap
[qi
];
342 // cout<<"Untracked answer: "<<qi<<endl;
349 uint32_t usecs
= (pr
.d_pheader
.ts
.tv_sec
- qd
.d_firstquestiontime
.tv_sec
) * 1000000 +
350 (pr
.d_pheader
.ts
.tv_usec
- qd
.d_firstquestiontime
.tv_usec
) ;
352 // cout<<"Usecs for "<<qi<<": "<<usecs<<endl;
353 if(!noservfailstats
|| header
.rcode
!= 2)
356 if(header
.rcode
!= 0 && header
.rcode
!=3)
358 ComboAddress rem
= pr
.getDest();
362 root
.submit(qname
, header
.rcode
, rem
);
365 if(!qd
.d_qcount
|| qd
.d_qcount
== qd
.d_answercount
) {
366 // cout<<"Clearing state for "<<qi<<endl<<endl;
370 // cout<<"State for qi remains open, qcount="<<qd.d_qcount<<", answercount="<<qd.d_answercount<<endl;
374 catch(std::exception
& e
) {
376 cout
<<"error parsing packet: "<<e
.what()<<endl
;
388 cout
<<"PCAP contained "<<pr
.d_correctpackets
<<" correct packets, "<<pr
.d_runts
<<" runts, "<< pr
.d_oversized
<<" oversize, "<<pr
.d_nonetheripudp
<<" non-UDP.\n";
393 cout<<"Open when done: "<<endl;
394 for(const auto& a : statmap) {
395 cout<<a.first<<": qcount="<<a.second.d_qcount<<", answercount="<<a.second.d_answercount<<endl;
399 cout
<<"Timespan: "<<(highestTime
-lowestTime
)/3600.0<<" hours"<<endl
;
401 cout
<<nonDNSIP
<<" non-DNS UDP, "<<dnserrors
<<" dns decoding errors, "<<parsefail
<<" packets failed to parse"<<endl
;
402 cout
<<"Ignored fragment packets: "<<fragmented
<<endl
;
403 cout
<<"Dropped DNS packets based on recursion-desired filter: "<<rdFilterMismatch
<<endl
;
404 if(!filtername
.empty())
405 cout
<<"Dropped DNS packets because not part of '"<<filtername
<<"': "<<nameMismatch
<< endl
;
406 cout
<<"DNS IPv4: "<<ipv4DNSPackets
<<" packets, IPv6: "<<ipv6DNSPackets
<<" packets"<<endl
;
407 cout
<<"Questions: "<<queries
<<", answers: "<<answers
<<endl
;
408 cout
<<"Reuses of same state entry: "<<reuses
<<endl
;
409 unsigned int unanswered
=0;
412 // ofstream openf("openf");
413 for(statmap_t::const_iterator i
=statmap
.begin(); i
!=statmap
.end(); ++i
) {
414 if(!i
->second
.d_answercount
) {
417 //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;
420 cout
<< boost::format("%d (%.02f%% of all) queries did not request recursion") % nonRDQueries
% ((nonRDQueries
*100.0)/queries
) << endl
;
421 cout
<< rdNonRAAnswers
<< " answers had recursion desired bit set, but recursion available=0 (for "<<rdnonra
.size()<<" remotes)"<<endl
;
422 cout
<<statmap
.size()<<" queries went unanswered, of which "<< statmap
.size()-unanswered
<<" were answered on exact retransmit"<<endl
;
423 cout
<<untracked
<<" responses could not be matched to questions"<<endl
;
424 cout
<<edns
<<" questions requested EDNS processing, do=1: "<<dnssecOK
<<", ad=1: "<<dnssecAD
<<", cd=1: "<<dnssecCD
<<endl
;
427 cout
<<(boost::format("%1% %|25t|%2%") % "Rcode" % "Count\n");
428 for(rcodes_t::const_iterator i
=rcodes
.begin(); i
!=rcodes
.end(); ++i
)
429 cout
<<(boost::format("%s %|25t|%d %|35t|(%.1f%%)") % RCode::to_s(i
->first
) % i
->second
% (i
->second
*100.0/answers
))<<endl
;
433 // ofstream stats("stats");
436 for(cumul_t::const_iterator i
=cumul
.begin(); i
!=cumul
.end(); ++i
) {
437 // stats<<i->first<<"\t"<<(sum+=i->second)<<"\n";
439 tottime
+=i
->first
*i
->second
;
442 typedef map
<uint32_t, bool> done_t
;
444 for(auto a
: {50, 100, 200, 300, 400, 800, 1000, 2000, 4000, 8000, 32000, 64000, 256000, 1024000, 2048000})
447 cout
.setf(std::ios::fixed
);
451 #if HAVE_BOOST_GE_148
452 if(g_vm
.count("log-histogram")) {
453 string fname
= g_vm
["stats-dir"].as
<string
>()+"/log-histogram";
454 ofstream
loglog(fname
);
456 throw runtime_error("Unable to write statistics to "+fname
);
458 writeLogHistogramFile(cumul
, loglog
);
461 if(g_vm
.count("full-histogram")) {
462 string fname
=g_vm
["stats-dir"].as
<string
>()+"/full-histogram";
463 ofstream
loglog(fname
);
465 throw runtime_error("Unable to write statistics to "+fname
);
466 writeFullHistogramFile(cumul
, g_vm
["full-histogram"].as
<double>(), loglog
);
472 double lastperc
=0, perc
=0;
475 for(cumul_t::const_iterator i
=cumul
.begin(); i
!=cumul
.end(); ++i
) {
476 for(done_t::iterator j
=done
.begin(); j
!=done
.end(); ++j
) {
477 if(!j
->second
&& i
->first
> j
->first
) {
480 perc
=sum
*100.0/totpairs
;
482 cout
<< perc
<<"% of questions answered within " << j
->first
<< " usec (";
484 cout
<< perc
<<"% of questions answered within " << j
->first
/1000.0 << " msec (";
486 cout
<<perc
-lastperc
<<"%)\n";
487 lastperc
=sum
*100.0/totpairs
;
494 for(auto j
= done
.begin(); j
!= done
.end(); ++j
) {
496 perc
=sum
*100.0/totpairs
;
498 cout
<< perc
<<"% of questions answered within " << j
->first
<< " usec (";
500 cout
<< perc
<<"% of questions answered within " << j
->first
/1000.0 << " msec (";
502 cout
<<perc
-lastperc
<<"%)\n";
503 lastperc
=sum
*100.0/totpairs
;
509 cout
<< (totpairs
-lastsum
)<<" responses ("<<((totpairs
-lastsum
)*100.0/answers
) <<"%) older than "<< (done
.rbegin()->first
/1000000.0) <<" seconds"<<endl
;
511 cout
<<"Average non-late response time: "<<tottime
/totpairs
<<" usec"<<endl
;
513 if(!g_vm
["load-stats"].as
<string
>().empty()) {
514 ofstream
load(g_vm
["load-stats"].as
<string
>().c_str());
516 throw runtime_error("Error writing load statistics to "+g_vm
["load-stats"].as
<string
>());
517 for(pcounts_t::value_type
& val
: pcounts
) {
518 load
<<val
.first
<<'\t'<<val
.second
.questions
<<'\t'<<val
.second
.answers
<<'\t'<<val
.second
.outstanding
<<'\n';
523 cout
<<"Saw questions from "<<requestors
.size()<<" distinct remotes, answers to "<<recipients
.size()<<endl
;
524 ofstream
remotes("remotes");
525 for(const ComboAddress
& rem
: requestors
) {
526 remotes
<<rem
.toString()<<'\n';
529 vector
<ComboAddress
> diff
;
530 set_difference(requestors
.begin(), requestors
.end(), recipients
.begin(), recipients
.end(), back_inserter(diff
), ComboAddress::addressOnlyLessThan());
531 cout
<<"Saw "<<diff
.size()<<" unique remotes asking questions, but not getting RA answers"<<endl
;
533 ofstream
ignored("ignored");
534 for(const ComboAddress
& rem
: diff
) {
535 ignored
<<rem
.toString()<<'\n';
537 ofstream
rdnonrafs("rdnonra");
538 for(const ComboAddress
& rem
: rdnonra
) {
539 rdnonrafs
<<rem
.toString()<<'\n';
544 root
.visit(visitor
, node
);
548 catch(std::exception
& e
)
550 cerr
<<"Fatal: "<<e
.what()<<endl
;