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.
28 #include "dnsparser.hh"
30 #include <boost/tuple/tuple.hpp>
31 #include <boost/tuple/tuple_comparison.hpp>
37 #include <boost/program_options.hpp>
39 #include <boost/logic/tribool.hpp>
40 #include "arguments.hh"
41 #include "namespaces.hh"
43 #include "dnsrecords.hh"
44 #include "statnode.hh"
46 namespace po
= boost::program_options
;
47 po::variables_map g_vm
;
51 static ArgvMap theArg
;
60 QuestionData() : d_qcount(0), d_answercount(0)
62 d_firstquestiontime
.tv_sec
=0;
68 struct pdns_timeval d_firstquestiontime
;
71 typedef map
<QuestionIdentifier
, QuestionData
> statmap_t
;
74 unsigned int liveQuestions()
77 for(statmap_t::value_type
& val
: statmap
) {
78 if(!val
.second
.d_answercount
)
80 // if(val.second.d_qcount > val.second.d_answercount)
81 // ret+= val.second.d_qcount - val.second.d_answercount;
88 unsigned int questions
;
90 unsigned int outstanding
;
94 questions
=answers
=outstanding
=0;
97 LiveCounts
operator-(const LiveCounts
& rhs
)
100 ret
.questions
= questions
- rhs
.questions
;
101 ret
.answers
= answers
- rhs
.answers
;
102 ret
.outstanding
= outstanding
;
107 void visitor(const StatNode
* node
, const StatNode::Stat
& selfstat
, const StatNode::Stat
& childstat
)
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();
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
;
123 int main(int argc
, char** argv
)
126 po::options_description
desc("Allowed options"), hidden
, alloptions
;
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");
139 ("files", po::value
<vector
<string
> >(), "files");
141 alloptions
.add(desc
).add(hidden
);
143 po::positional_options_description p
;
146 po::store(po::command_line_parser(argc
, argv
).options(alloptions
).positional(p
).run(), g_vm
);
149 vector
<string
> files
;
150 if(g_vm
.count("files"))
151 files
= g_vm
["files"].as
<vector
<string
> >();
153 if(g_vm
.count("version")) {
154 cerr
<<"dnsscope "<<VERSION
<<endl
;
158 if(files
.empty() || g_vm
.count("help")) {
159 cerr
<<"Syntax: dnsscope filename.pcap"<<endl
;
160 cout
<< desc
<< endl
;
166 bool verbose
= g_vm
.count("verbose");
168 bool haveRDFilter
=0, rdFilter
=0;
169 if(g_vm
.count("rd")) {
170 rdFilter
= g_vm
["rd"].as
<bool>();
172 cout
<<"Filtering on recursion desired="<<rdFilter
<<endl
;
175 cout
<<"Warning, looking at both RD and non-RD traffic!"<<endl
;
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
;
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
;
191 time_t lowestTime
=2000000000, highestTime
=0;
193 LiveCounts lastcounts
;
194 set
<ComboAddress
, ComboAddress::addressOnlyLessThan
> requestors
, recipients
, rdnonra
;
195 typedef vector
<pair
<time_t, LiveCounts
> > pcounts_t
;
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
);
205 while(pr
.getUDPPacket()) {
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) &&
211 if((pr
.d_ip
->ip_v
== 4 && !doIPv4
) || (pr
.d_ip
->ip_v
== 6 && !doIPv6
))
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
220 MOADNSParser
mdp(false, (const char*)pr
.d_payload
, pr
.d_len
);
221 if(haveRDFilter
&& mdp
.d_header
.rd
!= rdFilter
) {
226 if(!mdp
.d_header
.qr
&& getEDNSOpts(mdp
, &edo
)) {
228 if(edo
.d_Z
& EDNSOpts::DNSSECOK
)
237 if(pr
.d_ip
->ip_v
== 4)
242 if(pr
.d_pheader
.ts
.tv_sec
!= lastsec
) {
245 lc
.questions
= queries
;
246 lc
.answers
= answers
;
247 lc
.outstanding
= liveQuestions();
249 LiveCounts diff
= lc
- lastcounts
;
250 pcounts
.push_back(make_pair(pr
.d_pheader
.ts
.tv_sec
, diff
));
253 lastsec
= pr
.d_pheader
.ts
.tv_sec
;
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
);
260 string name
=mdp
.d_qname
.toString()+"|"+DNSRecordContent::NumberToType(mdp
.d_qtype
);
262 QuestionIdentifier qi
=QuestionIdentifier::create(pr
.getSource(), pr
.getDest(), mdp
);
264 if(!mdp
.d_header
.qr
) { // question
269 ComboAddress rem
= pr
.getSource();
271 requestors
.insert(rem
);
273 QuestionData
& qd
=statmap
[qi
];
275 if(!qd
.d_firstquestiontime
.tv_sec
)
276 qd
.d_firstquestiontime
=pr
.d_pheader
.ts
;
280 rcodes
[mdp
.d_header
.rcode
]++;
282 if(mdp
.d_header
.rd
&& !mdp
.d_header
.ra
) {
284 rdnonra
.insert(pr
.getDest());
287 if(mdp
.d_header
.ra
) {
288 ComboAddress rem
= pr
.getDest();
290 recipients
.insert(rem
);
293 QuestionData
& qd
=statmap
[qi
];
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";
309 if(mdp
.d_header
.rcode
!= 0 && mdp
.d_header
.rcode
!=3)
311 ComboAddress rem
= pr
.getDest();
315 root
.submit(mdp
.d_qname
, mdp
.d_header
.rcode
, rem
);
318 if(!qd
.d_qcount
|| qd
.d_qcount
== qd
.d_answercount
)
324 catch(MOADNSException
& mde
) {
326 cout
<<"error parsing packet: "<<mde
.what()<<endl
;
332 catch(std::exception
& e
) {
334 cout
<<"error parsing packet: "<<e
.what()<<endl
;
346 cout
<<"PCAP contained "<<pr
.d_correctpackets
<<" correct packets, "<<pr
.d_runts
<<" runts, "<< pr
.d_oversized
<<" oversize, "<<pr
.d_nonetheripudp
<<" non-UDP.\n";
349 cout
<<"Timespan: "<<(highestTime
-lowestTime
)/3600.0<<" hours"<<endl
;
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;
359 // ofstream openf("openf");
360 for(statmap_t::const_iterator i
=statmap
.begin(); i
!=statmap
.end(); ++i
) {
361 if(!i
->second
.d_answercount
) {
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;
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
;
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
;
380 // ofstream stats("stats");
381 uint32_t totpackets
=reallylate
;
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
;
389 typedef map
<uint32_t, bool> done_t
;
407 cout
.setf(std::ios::fixed
);
411 double lastperc
=0, perc
=0;
412 for(cumul_t::const_iterator i
=cumul
.begin(); i
!=cumul
.end(); ++i
) {
415 for(done_t::iterator j
=done
.begin(); j
!=done
.end(); ++j
)
416 if(!j
->second
&& i
->first
> j
->first
) {
419 perc
=sum
*100.0/totpackets
;
421 cout
<< perc
<<"% of questions answered within " << j
->first
<< " usec (";
423 cout
<< perc
<<"% of questions answered within " << j
->first
/1000.0 << " msec (";
425 cout
<<perc
-lastperc
<<"%)\n";
426 lastperc
=sum
*100.0/totpackets
;
429 cout
<<reallylate
<<" responses ("<<reallylate
*100.0/answers
<<"%) older than 2 seconds"<<endl
;
431 cout
<<"Average non-late response time: "<<tottime
/totpackets
<<" usec"<<endl
;
433 if(!g_vm
["load-stats"].as
<string
>().empty()) {
434 ofstream
load(g_vm
["load-stats"].as
<string
>().c_str());
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';
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';
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
;
453 ofstream
ignored("ignored");
454 for(const ComboAddress
& rem
: diff
) {
455 ignored
<<rem
.toString()<<'\n';
457 ofstream
rdnonrafs("rdnonra");
458 for(const ComboAddress
& rem
: rdnonra
) {
459 rdnonrafs
<<rem
.toString()<<'\n';
464 root
.visit(visitor
, node
);
468 catch(std::exception
& e
)
470 cerr
<<"Fatal: "<<e
.what()<<endl
;