]>
Commit | Line | Data |
---|---|---|
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 | */ | |
870a0fe4 AT |
22 | #ifdef HAVE_CONFIG_H |
23 | #include "config.h" | |
24 | #endif | |
dc125378 OM |
25 | #pragma GCC diagnostic push |
26 | #pragma GCC diagnostic ignored "-Wunused-parameter" | |
d65e98fd | 27 | #if __clang_major__ >= 15 |
dc125378 | 28 | #pragma GCC diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy" |
d65e98fd | 29 | #endif |
830515d8 BH |
30 | #include <boost/accumulators/accumulators.hpp> |
31 | #include <boost/array.hpp> | |
32 | #include <boost/accumulators/statistics.hpp> | |
dc125378 | 33 | #pragma GCC diagnostic pop |
b3bd2e75 | 34 | #include <boost/program_options.hpp> |
4dd35bb1 | 35 | #include "inflighter.cc" |
a4382ef3 | 36 | #include <deque> |
4dd35bb1 BH |
37 | #include "namespaces.hh" |
38 | #include "dnsparser.hh" | |
39 | #include "sstuff.hh" | |
40 | #include "misc.hh" | |
41 | #include "dnswriter.hh" | |
42 | #include "dnsrecords.hh" | |
d720eb8a OM |
43 | #include "dns_random.hh" |
44 | #include "arguments.hh" | |
4dd35bb1 | 45 | |
830515d8 | 46 | using namespace boost::accumulators; |
b3bd2e75 | 47 | namespace po = boost::program_options; |
de43ec0f | 48 | |
b3bd2e75 | 49 | po::variables_map g_vm; |
830515d8 | 50 | |
4dd35bb1 BH |
51 | StatBag S; |
52 | ||
d720eb8a OM |
53 | ArgvMap &arg() |
54 | { | |
55 | static ArgvMap theArg; | |
56 | return theArg; | |
57 | } | |
58 | ||
b3bd2e75 PD |
59 | bool g_quiet=false; |
60 | bool g_envoutput=false; | |
61 | ||
4dd35bb1 BH |
62 | struct DNSResult |
63 | { | |
64 | vector<ComboAddress> ips; | |
014516a0 | 65 | int rcode{0}; |
24a6058d | 66 | bool seenauthsoa{false}; |
4dd35bb1 BH |
67 | }; |
68 | ||
62921444 BH |
69 | struct TypedQuery |
70 | { | |
71 | TypedQuery(const string& name_, uint16_t type_) : name(name_), type(type_){} | |
a9456e80 | 72 | DNSName name; |
62921444 BH |
73 | uint16_t type; |
74 | }; | |
830515d8 | 75 | |
4dd35bb1 BH |
76 | struct SendReceive |
77 | { | |
014516a0 RG |
78 | using Identifier = int; |
79 | using Answer = DNSResult; // ip | |
80 | Socket d_socket; | |
dc593046 | 81 | std::deque<uint16_t> d_idqueue; |
d65e98fd | 82 | |
014516a0 | 83 | using acc_t = accumulator_set< |
830515d8 BH |
84 | double |
85 | , stats<boost::accumulators::tag::extended_p_square, | |
232f0877 | 86 | boost::accumulators::tag::median(with_p_square_quantile), |
830515d8 | 87 | boost::accumulators::tag::mean(immediate) |
232f0877 | 88 | > |
014516a0 | 89 | >; |
dd27dc73 | 90 | unique_ptr<acc_t> d_acc; |
014516a0 RG |
91 | |
92 | static constexpr std::array<double, 11> s_probs{{0.001,0.01, 0.025, 0.1, 0.25,0.5,0.75,0.9,0.975, 0.99,0.9999}}; | |
93 | unsigned int d_errors{0}; | |
94 | unsigned int d_nxdomains{0}; | |
95 | unsigned int d_nodatas{0}; | |
96 | unsigned int d_oks{0}; | |
97 | unsigned int d_unknowns{0}; | |
98 | unsigned int d_received{0}; | |
99 | unsigned int d_receiveerrors{0}; | |
100 | unsigned int d_senderrors{0}; | |
d65e98fd | 101 | |
014516a0 RG |
102 | SendReceive(const std::string& remoteAddr, uint16_t port) : |
103 | d_socket(AF_INET, SOCK_DGRAM), | |
104 | d_acc(make_unique<acc_t>(acc_t(boost::accumulators::tag::extended_p_square::probabilities=s_probs))) | |
4dd35bb1 | 105 | { |
014516a0 | 106 | d_socket.setReuseAddr(); |
910d27bb | 107 | ComboAddress remote(remoteAddr, port); |
014516a0 RG |
108 | d_socket.connect(remote); |
109 | for (unsigned int id =0 ; id < std::numeric_limits<uint16_t>::max(); ++id) { | |
a4382ef3 | 110 | d_idqueue.push_back(id); |
014516a0 | 111 | } |
4dd35bb1 | 112 | } |
d65e98fd | 113 | |
62921444 | 114 | Identifier send(TypedQuery& domain) |
4dd35bb1 BH |
115 | { |
116 | //cerr<<"Sending query for '"<<domain<<"'"<<endl; | |
d65e98fd | 117 | |
4dd35bb1 BH |
118 | // send it, copy code from 'sdig' |
119 | vector<uint8_t> packet; | |
d65e98fd | 120 | |
62921444 | 121 | DNSPacketWriter pw(packet, domain.name, domain.type); |
bf2aaa52 | 122 | |
014516a0 | 123 | if (d_idqueue.empty()) { |
bf2aaa52 BH |
124 | cerr<<"Exhausted ids!"<<endl; |
125 | exit(1); | |
d65e98fd | 126 | } |
a4382ef3 BH |
127 | pw.getHeader()->id = d_idqueue.front(); |
128 | d_idqueue.pop_front(); | |
4dd35bb1 BH |
129 | pw.getHeader()->rd = 1; |
130 | pw.getHeader()->qr = 0; | |
d65e98fd | 131 | |
014516a0 | 132 | if (::send(d_socket.getHandle(), &*packet.begin(), packet.size(), 0) < 0) { |
bf2aaa52 | 133 | d_senderrors++; |
014516a0 | 134 | } |
d65e98fd | 135 | |
014516a0 | 136 | if (!g_quiet) { |
a9456e80 | 137 | cout<<"Sent out query for '"<<domain.name<<"' with id "<<pw.getHeader()->id<<endl; |
014516a0 | 138 | } |
4dd35bb1 BH |
139 | return pw.getHeader()->id; |
140 | } | |
d65e98fd | 141 | |
ba2b8a7a | 142 | bool receive(Identifier& iden, DNSResult& dnsResult) |
4dd35bb1 | 143 | { |
014516a0 | 144 | if (waitForData(d_socket.getHandle(), 0, 500000) > 0) { |
e2ecb440 | 145 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init): no need to initialize the buffer |
014516a0 | 146 | std::array<char, 512> buf; |
d65e98fd | 147 | |
014516a0 RG |
148 | auto len = recv(d_socket.getHandle(), buf.data(), buf.size(), 0); |
149 | if (len < 0) { | |
9cb47734 | 150 | d_receiveerrors++; |
014516a0 | 151 | return false; |
9cb47734 | 152 | } |
014516a0 | 153 | d_received++; |
d65e98fd FM |
154 | // parse packet, set 'id', fill out 'ip' |
155 | ||
014516a0 | 156 | MOADNSParser mdp(false, string(buf.data(), static_cast<size_t>(len))); |
ba2b8a7a FM |
157 | if (!g_quiet) { |
158 | cout << "Reply to question for qname='" << mdp.d_qname << "', qtype=" << DNSRecordContent::NumberToType(mdp.d_qtype) << endl; | |
159 | cout << "Rcode: " << mdp.d_header.rcode << ", RD: " << mdp.d_header.rd << ", QR: " << mdp.d_header.qr; | |
160 | cout << ", TC: " << mdp.d_header.tc << ", AA: " << mdp.d_header.aa << ", opcode: " << mdp.d_header.opcode << endl; | |
b3bd2e75 | 161 | } |
ba2b8a7a FM |
162 | dnsResult.rcode = mdp.d_header.rcode; |
163 | for (auto i = mdp.d_answers.begin(); i != mdp.d_answers.end(); ++i) { | |
164 | if (i->first.d_place == 1 && i->first.d_type == mdp.d_qtype) { | |
165 | dnsResult.ips.emplace_back(i->first.getContent()->getZoneRepresentation()); | |
d712a557 | 166 | } |
ba2b8a7a FM |
167 | if (i->first.d_place == 2 && i->first.d_type == QType::SOA) { |
168 | dnsResult.seenauthsoa = true; | |
169 | } | |
170 | if (!g_quiet) { | |
171 | cout << i->first.d_place - 1 << "\t" << i->first.d_name << "\tIN\t" << DNSRecordContent::NumberToType(i->first.d_type); | |
172 | cout << "\t" << i->first.d_ttl << "\t" << i->first.getContent()->getZoneRepresentation() << "\n"; | |
b3bd2e75 | 173 | } |
4dd35bb1 | 174 | } |
d65e98fd | 175 | |
ba2b8a7a FM |
176 | iden = mdp.d_header.id; |
177 | d_idqueue.push_back(iden); | |
d65e98fd | 178 | |
014516a0 | 179 | return true; |
4dd35bb1 | 180 | } |
ba2b8a7a | 181 | |
014516a0 | 182 | return false; |
4dd35bb1 | 183 | } |
d65e98fd | 184 | |
a4382ef3 BH |
185 | void deliverTimeout(const Identifier& id) |
186 | { | |
a9456e80 | 187 | if(!g_quiet) { |
188 | cout<<"Timeout for id "<<id<<endl; | |
189 | } | |
a4382ef3 BH |
190 | d_idqueue.push_back(id); |
191 | } | |
d65e98fd | 192 | |
ba2b8a7a | 193 | void deliverAnswer(TypedQuery& domain, const DNSResult& dnsResult, unsigned int usec) |
4dd35bb1 | 194 | { |
ba2b8a7a FM |
195 | (*d_acc)(usec / 1000.0); |
196 | // if(usec > 1000000) | |
197 | // cerr<<"Slow: "<<domain<<" ("<<usec/1000.0<<" ms)\n"; | |
198 | if (!g_quiet) { | |
199 | cout << domain.name << "|" << DNSRecordContent::NumberToType(domain.type) << ": (" << usec / 1000.0 << " ms) rcode: " << dnsResult.rcode; | |
200 | for (const ComboAddress& comboAddress : dnsResult.ips) { | |
201 | cout << ", " << comboAddress.toString(); | |
b3bd2e75 | 202 | } |
ba2b8a7a | 203 | cout << endl; |
4dd35bb1 | 204 | } |
ba2b8a7a | 205 | if (dnsResult.rcode == RCode::NXDomain) { |
d712a557 BH |
206 | d_nxdomains++; |
207 | } | |
ba2b8a7a | 208 | else if (dnsResult.rcode != 0) { |
910d27bb | 209 | d_errors++; |
d712a557 | 210 | } |
ba2b8a7a | 211 | else if (dnsResult.ips.empty() && dnsResult.seenauthsoa) { |
d712a557 | 212 | d_nodatas++; |
ba2b8a7a FM |
213 | } |
214 | else if (!dnsResult.ips.empty()) { | |
910d27bb | 215 | d_oks++; |
ba2b8a7a | 216 | } |
9cb47734 | 217 | else { |
ba2b8a7a FM |
218 | if (!g_quiet) { |
219 | cout << "UNKNOWN!! ^^" << endl; | |
220 | } | |
9cb47734 BH |
221 | d_unknowns++; |
222 | } | |
4dd35bb1 BH |
223 | } |
224 | }; | |
225 | ||
050e6877 | 226 | static void usage(po::options_description &desc) { |
d2c701a0 PL |
227 | cerr << "Usage: dnsbulktest [OPTION].. IPADDRESS PORTNUMBER [LIMIT]"<<endl; |
228 | cerr << desc << "\n"; | |
229 | } | |
230 | ||
910d27bb | 231 | int main(int argc, char** argv) |
fc9152ee | 232 | try |
4dd35bb1 | 233 | { |
fe810bcf RG |
234 | ::arg().set("rng", "Specify random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.")="auto"; |
235 | ::arg().set("entropy-source", "If set, read entropy from this file")="/dev/urandom"; | |
236 | ||
b3bd2e75 PD |
237 | po::options_description desc("Allowed options"); |
238 | desc.add_options() | |
239 | ("help,h", "produce help message") | |
240 | ("quiet,q", "be quiet about individual queries") | |
62921444 | 241 | ("type,t", po::value<string>()->default_value("A"), "What type to query for") |
b3bd2e75 | 242 | ("envoutput,e", "write report in shell environment format") |
d2c701a0 | 243 | ("version", "show the version number") |
503c33df | 244 | ("www", po::value<bool>()->default_value(true), "duplicate all queries with an additional 'www.' in front") |
b3bd2e75 PD |
245 | ; |
246 | ||
247 | po::options_description alloptions; | |
248 | po::options_description hidden("hidden options"); | |
249 | hidden.add_options() | |
250 | ("ip-address", po::value<string>(), "ip-address") | |
251 | ("portnumber", po::value<uint16_t>(), "portnumber") | |
252 | ("limit", po::value<uint32_t>()->default_value(0), "limit"); | |
253 | ||
254 | alloptions.add(desc).add(hidden); | |
255 | po::positional_options_description p; | |
256 | p.add("ip-address", 1); | |
257 | p.add("portnumber", 1); | |
258 | p.add("limit", 1); | |
259 | ||
260 | po::store(po::command_line_parser(argc, argv).options(alloptions).positional(p).run(), g_vm); | |
261 | po::notify(g_vm); | |
262 | ||
263 | if (g_vm.count("help")) { | |
d2c701a0 | 264 | usage(desc); |
b3bd2e75 | 265 | return EXIT_SUCCESS; |
ac379ae2 | 266 | } |
d2c701a0 PL |
267 | |
268 | if (g_vm.count("version")) { | |
269 | cerr<<"dnsbulktest "<<VERSION<<endl; | |
270 | return EXIT_SUCCESS; | |
271 | } | |
272 | ||
b3bd2e75 PD |
273 | if(!g_vm.count("portnumber")) { |
274 | cerr<<"Fatal, need to specify ip-address and portnumber"<<endl; | |
d2c701a0 | 275 | usage(desc); |
b3bd2e75 PD |
276 | return EXIT_FAILURE; |
277 | } | |
278 | ||
faf7fa43 | 279 | bool doWww = g_vm["www"].as<bool>(); |
62921444 BH |
280 | g_quiet = g_vm.count("quiet") > 0; |
281 | g_envoutput = g_vm.count("envoutput") > 0; | |
282 | uint16_t qtype; | |
283 | reportAllTypes(); | |
284 | try { | |
285 | qtype = DNSRecordContent::TypeToNumber(g_vm["type"].as<string>()); | |
286 | } | |
287 | catch(std::exception& e) { | |
288 | cerr << e.what() << endl; | |
289 | return EXIT_FAILURE; | |
290 | } | |
b3bd2e75 PD |
291 | |
292 | SendReceive sr(g_vm["ip-address"].as<string>(), g_vm["portnumber"].as<uint16_t>()); | |
293 | unsigned int limit = g_vm["limit"].as<unsigned int>(); | |
d65e98fd | 294 | |
62921444 | 295 | vector<TypedQuery> domains; |
d65e98fd | 296 | |
62921444 | 297 | Inflighter<vector<TypedQuery>, SendReceive> inflighter(domains, sr); |
0eafd0fe | 298 | inflighter.d_maxInFlight = 1000; |
bf2aaa52 | 299 | inflighter.d_timeoutSeconds = 3; |
0eafd0fe | 300 | inflighter.d_burst = 100; |
4dd35bb1 | 301 | string line; |
d65e98fd | 302 | |
4dd35bb1 | 303 | pair<string, string> split; |
013f96e9 | 304 | string::size_type pos; |
4dd35bb1 | 305 | while(stringfgets(stdin, line)) { |
910d27bb BH |
306 | if(limit && domains.size() >= limit) |
307 | break; | |
246ccf7b | 308 | |
dc593046 | 309 | boost::trim_right(line); |
246ccf7b KM |
310 | if(line.empty() || line[0] == '#') |
311 | continue; | |
4dd35bb1 | 312 | split=splitField(line,','); |
246ccf7b KM |
313 | if (split.second.empty()) |
314 | split=splitField(line,'\t'); | |
1ee9e847 | 315 | if(split.second.find('.') == 0) // skip 'Hidden profile' in quantcast list. |
246ccf7b | 316 | continue; |
013f96e9 | 317 | pos=split.second.find('/'); |
246ccf7b | 318 | if(pos != string::npos) // alexa has whole urls in the list now. |
013f96e9 | 319 | split.second.resize(pos); |
6fc9c5ea BH |
320 | if(find_if(split.second.begin(), split.second.end(), isalpha) == split.second.end()) |
321 | { | |
322 | continue; // this was an IP address | |
323 | } | |
62921444 | 324 | domains.push_back(TypedQuery(split.second, qtype)); |
faf7fa43 | 325 | if(doWww) |
326 | domains.push_back(TypedQuery("www."+split.second, qtype)); | |
4dd35bb1 BH |
327 | } |
328 | cerr<<"Read "<<domains.size()<<" domains!"<<endl; | |
d720eb8a | 329 | shuffle(domains.begin(), domains.end(), pdns::dns_random_engine()); |
4dd35bb1 | 330 | |
bf2aaa52 BH |
331 | boost::format datafmt("%s %|20t|%+15s %|40t|%s %|60t|%+15s\n"); |
332 | ||
4dd35bb1 BH |
333 | for(;;) { |
334 | try { | |
335 | inflighter.run(); | |
336 | break; | |
337 | } | |
830515d8 | 338 | catch(std::exception& e) { |
4dd35bb1 BH |
339 | cerr<<"Caught exception: "<<e.what()<<endl; |
340 | } | |
341 | } | |
9cb47734 | 342 | |
bf2aaa52 | 343 | cerr<< datafmt % "Sending" % "" % "Receiving" % ""; |
46d77a35 | 344 | cerr<< datafmt % " Queued " % domains.size() % " Received" % sr.d_received; |
bf2aaa52 BH |
345 | cerr<< datafmt % " Error -/-" % sr.d_senderrors % " Timeouts" % inflighter.getTimeouts(); |
346 | cerr<< datafmt % " " % "" % " Unexpected" % inflighter.getUnexpecteds(); | |
d65e98fd | 347 | |
46d77a35 | 348 | cerr<< datafmt % " Sent" % (domains.size() - sr.d_senderrors) % " Total" % (sr.d_received + inflighter.getTimeouts() + inflighter.getUnexpecteds()); |
d65e98fd FM |
349 | |
350 | cerr<<endl; | |
bf2aaa52 BH |
351 | cerr<< datafmt % "DNS Status" % "" % "" % ""; |
352 | cerr<< datafmt % " OK" % sr.d_oks % "" % ""; | |
d65e98fd FM |
353 | cerr<< datafmt % " Error" % sr.d_errors % "" % ""; |
354 | cerr<< datafmt % " No Data" % sr.d_nodatas % "" % ""; | |
bf2aaa52 | 355 | cerr<< datafmt % " NXDOMAIN" % sr.d_nxdomains % "" % ""; |
d65e98fd | 356 | cerr<< datafmt % " Unknowns" % sr.d_unknowns % "" % ""; |
bf2aaa52 BH |
357 | cerr<< datafmt % "Answers" % (sr.d_oks + sr.d_errors + sr.d_nodatas + sr.d_nxdomains + sr.d_unknowns) % "" % ""; |
358 | cerr<< datafmt % " Timeouts " % (inflighter.getTimeouts()) % "" % ""; | |
359 | cerr<< datafmt % "Total " % (sr.d_oks + sr.d_errors + sr.d_nodatas + sr.d_nxdomains + sr.d_unknowns + inflighter.getTimeouts()) % "" % ""; | |
d65e98fd | 360 | |
830515d8 | 361 | cerr<<"\n"; |
9273c94d | 362 | cerr<< "Mean response time: "<<mean(*sr.d_acc) << " ms"<<", median: "<<median(*sr.d_acc)<< " ms\n"; |
d65e98fd | 363 | |
9273c94d | 364 | boost::format statfmt("Time < %6.03f ms %|30t|%6.03f%% cumulative\n"); |
d65e98fd | 365 | |
014516a0 | 366 | for (unsigned int i = 0; i < SendReceive::s_probs.size(); ++i) { |
e2ecb440 | 367 | cerr << statfmt % extended_p_square(*sr.d_acc)[i] % (100*SendReceive::s_probs.at(i)); |
014516a0 | 368 | } |
830515d8 | 369 | |
014516a0 | 370 | if (g_envoutput) { |
b3bd2e75 PD |
371 | cout<<"DBT_QUEUED="<<domains.size()<<endl; |
372 | cout<<"DBT_SENDERRORS="<<sr.d_senderrors<<endl; | |
46d77a35 | 373 | cout<<"DBT_RECEIVED="<<sr.d_received<<endl; |
aa395172 | 374 | cout<<"DBT_NXDOMAINS="<<sr.d_nxdomains<<endl; |
375 | cout<<"DBT_NODATAS="<<sr.d_nodatas<<endl; | |
376 | cout<<"DBT_UNKNOWNS="<<sr.d_unknowns<<endl; | |
377 | cout<<"DBT_OKS="<<sr.d_oks<<endl; | |
378 | cout<<"DBT_ERRORS="<<sr.d_errors<<endl; | |
b3bd2e75 PD |
379 | cout<<"DBT_TIMEOUTS="<<inflighter.getTimeouts()<<endl; |
380 | cout<<"DBT_UNEXPECTEDS="<<inflighter.getUnexpecteds()<<endl; | |
bbb6ce1f | 381 | cout<<"DBT_OKPERCENTAGE="<<((float)sr.d_oks/domains.size()*100)<<endl; |
dfddcdaf | 382 | cout<<"DBT_OKPERCENTAGEINT="<<(int)((float)sr.d_oks/domains.size()*100)<<endl; |
b3bd2e75 | 383 | } |
4dd35bb1 | 384 | } |
014516a0 | 385 | catch (const PDNSException& exp) |
fc9152ee | 386 | { |
014516a0 | 387 | cerr<<"Fatal error: "<<exp.reason<<endl; |
e2ecb440 | 388 | _exit(EXIT_FAILURE); |
014516a0 RG |
389 | } |
390 | catch (const std::exception& exp) { | |
391 | cerr<<"Fatal error: "<<exp.what()<<endl; | |
e2ecb440 | 392 | _exit(EXIT_FAILURE); |
fc9152ee | 393 | } |