7 #include "dnsparser.hh"
9 #include <boost/tuple/tuple.hpp>
10 #include <boost/tuple/tuple_comparison.hpp>
16 #include <boost/program_options.hpp>
18 #include <boost/logic/tribool.hpp>
19 #include "arguments.hh"
20 #include "namespaces.hh"
22 #include "dnsrecords.hh"
23 #include "statnode.hh"
25 namespace po
= boost::program_options
;
26 po::variables_map g_vm
;
30 static ArgvMap theArg
;
39 QuestionData() : d_qcount(0), d_answercount(0)
41 d_firstquestiontime
.tv_sec
=0;
47 struct pdns_timeval d_firstquestiontime
;
50 typedef map
<QuestionIdentifier
, QuestionData
> statmap_t
;
53 unsigned int liveQuestions()
56 for(statmap_t::value_type
& val
: statmap
) {
57 if(!val
.second
.d_answercount
)
59 // if(val.second.d_qcount > val.second.d_answercount)
60 // ret+= val.second.d_qcount - val.second.d_answercount;
67 unsigned int questions
;
69 unsigned int outstanding
;
73 questions
=answers
=outstanding
=0;
76 LiveCounts
operator-(const LiveCounts
& rhs
)
79 ret
.questions
= questions
- rhs
.questions
;
80 ret
.answers
= answers
- rhs
.answers
;
81 ret
.outstanding
= outstanding
;
86 void visitor(const StatNode
* node
, const StatNode::Stat
& selfstat
, const StatNode::Stat
& childstat
)
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();
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
;
102 int main(int argc
, char** argv
)
105 po::options_description
desc("Allowed options"), hidden
, alloptions
;
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");
118 ("files", po::value
<vector
<string
> >(), "files");
120 alloptions
.add(desc
).add(hidden
);
122 po::positional_options_description p
;
125 po::store(po::command_line_parser(argc
, argv
).options(alloptions
).positional(p
).run(), g_vm
);
128 vector
<string
> files
;
129 if(g_vm
.count("files"))
130 files
= g_vm
["files"].as
<vector
<string
> >();
132 if(g_vm
.count("version")) {
133 cerr
<<"dnsscope "<<VERSION
<<endl
;
137 if(files
.empty() || g_vm
.count("help")) {
138 cerr
<<"Syntax: dnsscope filename.pcap"<<endl
;
139 cout
<< desc
<< endl
;
145 bool verbose
= g_vm
.count("verbose");
147 bool haveRDFilter
=0, rdFilter
=0;
148 if(g_vm
.count("rd")) {
149 rdFilter
= g_vm
["rd"].as
<bool>();
151 cout
<<"Filtering on recursion desired="<<rdFilter
<<endl
;
154 cout
<<"Warning, looking at both RD and non-RD traffic!"<<endl
;
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
;
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
;
170 time_t lowestTime
=2000000000, highestTime
=0;
172 LiveCounts lastcounts
;
173 set
<ComboAddress
, ComboAddress::addressOnlyLessThan
> requestors
, recipients
, rdnonra
;
174 typedef vector
<pair
<time_t, LiveCounts
> > pcounts_t
;
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
);
184 while(pr
.getUDPPacket()) {
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) &&
190 if((pr
.d_ip
->ip_v
== 4 && !doIPv4
) || (pr
.d_ip
->ip_v
== 6 && !doIPv6
))
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
199 MOADNSParser
mdp((const char*)pr
.d_payload
, pr
.d_len
);
200 if(haveRDFilter
&& mdp
.d_header
.rd
!= rdFilter
) {
205 if(!mdp
.d_header
.qr
&& getEDNSOpts(mdp
, &edo
)) {
207 if(edo
.d_Z
& EDNSOpts::DNSSECOK
)
216 if(pr
.d_ip
->ip_v
== 4)
221 if(pr
.d_pheader
.ts
.tv_sec
!= lastsec
) {
224 lc
.questions
= queries
;
225 lc
.answers
= answers
;
226 lc
.outstanding
= liveQuestions();
228 LiveCounts diff
= lc
- lastcounts
;
229 pcounts
.push_back(make_pair(pr
.d_pheader
.ts
.tv_sec
, diff
));
232 lastsec
= pr
.d_pheader
.ts
.tv_sec
;
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
);
239 string name
=mdp
.d_qname
.toString()+"|"+DNSRecordContent::NumberToType(mdp
.d_qtype
);
241 QuestionIdentifier qi
=QuestionIdentifier::create(pr
.getSource(), pr
.getDest(), mdp
);
243 if(!mdp
.d_header
.qr
) { // question
248 ComboAddress rem
= pr
.getSource();
250 requestors
.insert(rem
);
252 QuestionData
& qd
=statmap
[qi
];
254 if(!qd
.d_firstquestiontime
.tv_sec
)
255 qd
.d_firstquestiontime
=pr
.d_pheader
.ts
;
259 rcodes
[mdp
.d_header
.rcode
]++;
261 if(mdp
.d_header
.rd
&& !mdp
.d_header
.ra
) {
263 rdnonra
.insert(pr
.getDest());
266 if(mdp
.d_header
.ra
) {
267 ComboAddress rem
= pr
.getDest();
269 recipients
.insert(rem
);
272 QuestionData
& qd
=statmap
[qi
];
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";
288 if(mdp
.d_header
.rcode
!= 0 && mdp
.d_header
.rcode
!=3)
290 ComboAddress rem
= pr
.getDest();
294 root
.submit(mdp
.d_qname
, mdp
.d_header
.rcode
, rem
);
297 if(!qd
.d_qcount
|| qd
.d_qcount
== qd
.d_answercount
)
303 catch(MOADNSException
& mde
) {
305 cout
<<"error parsing packet: "<<mde
.what()<<endl
;
311 catch(std::exception
& e
) {
313 cout
<<"error parsing packet: "<<e
.what()<<endl
;
325 cout
<<"PCAP contained "<<pr
.d_correctpackets
<<" correct packets, "<<pr
.d_runts
<<" runts, "<< pr
.d_oversized
<<" oversize, "<<pr
.d_nonetheripudp
<<" non-UDP.\n";
328 cout
<<"Timespan: "<<(highestTime
-lowestTime
)/3600.0<<" hours"<<endl
;
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;
338 // ofstream openf("openf");
339 for(statmap_t::const_iterator i
=statmap
.begin(); i
!=statmap
.end(); ++i
) {
340 if(!i
->second
.d_answercount
) {
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;
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
;
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
;
359 // ofstream stats("stats");
360 uint32_t totpackets
=reallylate
;
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
;
368 typedef map
<uint32_t, bool> done_t
;
386 cout
.setf(std::ios::fixed
);
390 double lastperc
=0, perc
=0;
391 for(cumul_t::const_iterator i
=cumul
.begin(); i
!=cumul
.end(); ++i
) {
394 for(done_t::iterator j
=done
.begin(); j
!=done
.end(); ++j
)
395 if(!j
->second
&& i
->first
> j
->first
) {
398 perc
=sum
*100.0/totpackets
;
400 cout
<< perc
<<"% of questions answered within " << j
->first
<< " usec (";
402 cout
<< perc
<<"% of questions answered within " << j
->first
/1000.0 << " msec (";
404 cout
<<perc
-lastperc
<<"%)\n";
405 lastperc
=sum
*100.0/totpackets
;
408 cout
<<reallylate
<<" responses ("<<reallylate
*100.0/answers
<<"%) older than 2 seconds"<<endl
;
410 cout
<<"Average non-late response time: "<<tottime
/totpackets
<<" usec"<<endl
;
412 if(!g_vm
["load-stats"].as
<string
>().empty()) {
413 ofstream
load(g_vm
["load-stats"].as
<string
>().c_str());
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';
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';
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
;
432 ofstream
ignored("ignored");
433 for(const ComboAddress
& rem
: diff
) {
434 ignored
<<rem
.toString()<<'\n';
436 ofstream
rdnonrafs("rdnonra");
437 for(const ComboAddress
& rem
: rdnonra
) {
438 rdnonrafs
<<rem
.toString()<<'\n';
443 root
.visit(visitor
, node
);
447 catch(std::exception
& e
)
449 cerr
<<"Fatal: "<<e
.what()<<endl
;