]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/calidns.cc
spelling: syscall
[thirdparty/pdns.git] / pdns / calidns.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 */
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
a7f7d29f
RG
25
26#include <atomic>
7d8a5de7 27#include <iostream>
a7f7d29f
RG
28#include <fstream>
29#include <memory>
30#include <poll.h>
31#include <thread>
32
33#include <boost/program_options.hpp>
34
35#include "dns_random.hh"
7d8a5de7 36#include "dnsparser.hh"
7d8a5de7 37#include "dnswriter.hh"
38#include "dnsrecords.hh"
a7f7d29f
RG
39#include "ednsoptions.hh"
40#include "ednssubnet.hh"
41#include "misc.hh"
42#include "sstuff.hh"
7d8a5de7 43#include "statbag.hh"
a7f7d29f 44
7d8a5de7 45using std::thread;
c1e527e3 46using std::unique_ptr;
7d8a5de7 47
48StatBag S;
49
325e59e7
RG
50static std::atomic<unsigned int> g_recvcounter, g_recvbytes;
51static volatile bool g_done;
7d8a5de7 52
c7c8bdee 53namespace po = boost::program_options;
325e59e7
RG
54static po::variables_map g_vm;
55
56static bool g_quiet;
c7c8bdee 57
a7f7d29f 58static void* recvThread(const vector<Socket*>* sockets)
7d8a5de7 59{
c1e527e3 60 vector<pollfd> rfds, fds;
61 for(const auto& s : *sockets) {
62 struct pollfd pfd;
63 pfd.fd = s->getHandle();
64 pfd.events = POLLIN;
65 pfd.revents = 0;
66 rfds.push_back(pfd);
67 }
68
69 int err;
70
71 vector<struct mmsghdr> buf(100);
72 for(auto& m : buf) {
73 fillMSGHdr(&m.msg_hdr, new struct iovec, new char[512], 512, new char[1500], 1500, new ComboAddress("127.0.0.1"));
74 }
75
7d8a5de7 76 while(!g_done) {
c1e527e3 77 fds=rfds;
78
79 err = poll(&fds[0], fds.size(), -1);
80 if(err < 0) {
81 if(errno==EINTR)
82 continue;
83 unixDie("Unable to poll for new UDP events");
84 }
c1e527e3 85
28c266da 86 for(auto &pfd : fds) {
c1e527e3 87 if(pfd.revents & POLLIN) {
88
89 if((err=recvmmsg(pfd.fd, &buf[0], buf.size(), MSG_WAITFORONE, 0)) < 0 ) {
90 if(errno != EAGAIN)
91 cerr<<"recvfrom gave error, ignoring: "<<strerror(errno)<<endl;
92 unixDie("recvmmsg");
93 continue;
94 }
3df2e6c4 95 g_recvcounter+=err;
96 for(int n=0; n < err; ++n)
97 g_recvbytes += buf[n].msg_len;
c1e527e3 98 }
7d8a5de7 99 }
7d8a5de7 100 }
c1e527e3 101
7d8a5de7 102 return 0;
103}
104
a7f7d29f 105static void setSocketBuffer(int fd, int optname, uint32_t size)
7d8a5de7 106{
107 uint32_t psize=0;
108 socklen_t len=sizeof(psize);
109
110 if(!getsockopt(fd, SOL_SOCKET, optname, (char*)&psize, &len) && psize > size) {
325e59e7
RG
111 if (!g_quiet) {
112 cerr<<"Not decreasing socket buffer size from "<<psize<<" to "<<size<<endl;
113 }
7d8a5de7 114 return;
115 }
116
325e59e7
RG
117 if (setsockopt(fd, SOL_SOCKET, optname, (char*)&size, sizeof(size)) < 0 ) {
118 if (!g_quiet) {
119 cerr<<"Warning: unable to raise socket buffer size to "<<size<<": "<<strerror(errno)<<endl;
120 }
121 }
7d8a5de7 122}
123
124
125static void setSocketReceiveBuffer(int fd, uint32_t size)
126{
127 setSocketBuffer(fd, SO_RCVBUF, size);
128}
129
130static void setSocketSendBuffer(int fd, uint32_t size)
131{
132 setSocketBuffer(fd, SO_SNDBUF, size);
133}
134
a7f7d29f
RG
135static ComboAddress getRandomAddressFromRange(const Netmask& ecsRange)
136{
137 ComboAddress result = ecsRange.getMaskedNetwork();
138 uint8_t bits = ecsRange.getBits();
139 uint32_t mod = 1 << (32 - bits);
140 result.sin4.sin_addr.s_addr = result.sin4.sin_addr.s_addr + ntohl(dns_random(mod));
141 return result;
142}
143
144static void replaceEDNSClientSubnet(vector<uint8_t>* packet, const Netmask& ecsRange)
145{
146 /* the last 4 bytes of the packet are the IPv4 address */
147 ComboAddress rnd = getRandomAddressFromRange(ecsRange);
148 uint32_t addr = rnd.sin4.sin_addr.s_addr;
149
150 const auto packetSize = packet->size();
151 if (packetSize < sizeof(addr)) {
152 return;
153 }
154
155 memcpy(&packet->at(packetSize - sizeof(addr)), &addr, sizeof(addr));
156}
157
158static void sendPackets(const vector<Socket*>* sockets, const vector<vector<uint8_t>* >& packets, int qps, ComboAddress dest, const Netmask& ecsRange)
e9a27786 159{
c1e527e3 160 unsigned int burst=100;
9e8a59fc 161 const auto nsecPerBurst=1*(unsigned long)(burst*1000000000.0/qps);
e9a27786 162 struct timespec nsec;
163 nsec.tv_sec=0;
9e8a59fc 164 nsec.tv_nsec=0;
e9a27786 165 int count=0;
9e8a59fc
KW
166 unsigned int nBursts=0;
167 DTime dt;
168 dt.set();
e9a27786 169
c1e527e3 170 struct Unit {
c1e527e3 171 struct msghdr msgh;
172 struct iovec iov;
173 char cbuf[256];
c1e527e3 174 };
ea550a82 175 vector<unique_ptr<Unit> > units;
176 int ret;
c1e527e3 177
fa5958af 178 for(const auto& p : packets) {
e9a27786 179 count++;
e9a27786 180
19f9117b 181 Unit u;
c1e527e3 182
a7f7d29f
RG
183 if (!ecsRange.empty()) {
184 replaceEDNSClientSubnet(p, ecsRange);
185 }
186
fa5958af 187 fillMSGHdr(&u.msgh, &u.iov, u.cbuf, 0, (char*)&(*p)[0], p->size(), &dest);
19f9117b 188 if((ret=sendmsg((*sockets)[count % sockets->size()]->getHandle(),
189 &u.msgh, 0)))
190 if(ret < 0)
191 unixDie("sendmmsg");
192
193
9e8a59fc
KW
194 if(!(count%burst)) {
195 nBursts++;
196 // Calculate the time in nsec we need to sleep to the next burst.
197 // If this is negative, it means that we are not achieving the requested
198 // target rate, in which case we skip the sleep.
199 int toSleep = nBursts*nsecPerBurst - 1000*dt.udiffNoReset();
200 if (toSleep > 0) {
201 nsec.tv_nsec = toSleep;
202 nanosleep(&nsec, 0);
203 }
204 }
e9a27786 205 }
206}
207
a7f7d29f 208static void usage(po::options_description &desc) {
1fe98901 209 cerr<<"Syntax: calidns [OPTIONS] QUERY_FILE DESTINATION INITIAL_QPS HITRATE"<<endl;
c7c8bdee 210 cerr<<desc<<endl;
32823984 211}
7d8a5de7 212
fa5958af 213/*
214 New plan. Set cache hit percentage, which we achieve on a per second basis.
215 So we start with 10000 qps for example, and for 90% cache hit ratio means
216 we take 1000 unique queries and each send them 10 times.
217
218 We then move the 1000 unique queries to the 'known' pool.
219
220 For the next second, say 20000 qps, we know we are going to need 2000 new queries,
221 so we take 2000 from the unknown pool. Then we need 18000 cache hits. We can get 1000 from
222 the known pool, leaving us down 17000. Or, we have 3000 in total now and we need 2000. We simply
223 repeat the 3000 mix we have ~7 times. The 2000 can now go to the known pool too.
224
225 For the next second, say 30000 qps, we'll need 3000 cache misses, which we get from
226 the unknown pool. To this we add 3000 queries from the known pool. Next up we repeat this batch 5
227 times.
228
229 In general the algorithm therefore is:
230
231 1) Calculate number of cache misses required, get them from the unknown pool
232 2) Move those to the known pool
233 3) Fill up to amount of queries we need with random picks from the known pool
234
235*/
7d8a5de7 236
237int main(int argc, char** argv)
238try
239{
c7c8bdee
PL
240 po::options_description desc("Options");
241 desc.add_options()
242 ("help,h", "Show this helpful message")
243 ("version", "Show the version number")
a7f7d29f 244 ("ecs", po::value<string>(), "Add EDNS Client Subnet option to outgoing queries using random addresses from the specified range (IPv4 only)")
a0149ecc 245 ("ecs-from-file", "Read IP or subnet values from the query file and add them as EDNS Client Subnet options to outgoing queries")
c7c8bdee 246 ("increment", po::value<float>()->default_value(1.1), "Set the factor to increase the QPS load per run")
f3f8331c 247 ("maximum-qps", po::value<uint32_t>(), "Stop incrementing once this rate has been reached, to provide a stable load")
ce39630b 248 ("minimum-success-rate", po::value<double>()->default_value(0), "Stop the test as soon as the success rate drops below this value, in percent")
4038b8d6 249 ("plot-file", po::value<string>(), "Write results to the specific file")
23165000 250 ("quiet", "Whether to run quietly, outputting only the maximum QPS reached. This option is mostly useful when used with --minimum-success-rate")
c7c8bdee
PL
251 ("want-recursion", "Set the Recursion Desired flag on queries");
252 po::options_description alloptions;
253 po::options_description hidden("hidden options");
254 hidden.add_options()
255 ("query-file", po::value<string>(), "File with queries")
256 ("destination", po::value<string>(), "Destination address")
d9ea45fc 257 ("initial-qps", po::value<uint32_t>(), "Initial number of queries per second")
c7c8bdee
PL
258 ("hitrate", po::value<double>(), "Aim this percent cache hitrate");
259
260 alloptions.add(desc).add(hidden);
261 po::positional_options_description p;
262 p.add("query-file", 1);
263 p.add("destination", 1);
d9ea45fc 264 p.add("initial-qps", 1);
c7c8bdee
PL
265 p.add("hitrate", 1);
266
267 po::store(po::command_line_parser(argc, argv).options(alloptions).positional(p).run(), g_vm);
268 po::notify(g_vm);
269
270 if (g_vm.count("help")) {
271 usage(desc);
272 return EXIT_SUCCESS;
1fe98901 273 }
32823984 274
c7c8bdee
PL
275 if (g_vm.count("version")) {
276 cerr<<"calidns "<<VERSION<<endl;
277 return EXIT_SUCCESS;
278 }
1fe98901 279
d9ea45fc 280 if (!(g_vm.count("query-file") && g_vm.count("destination") && g_vm.count("initial-qps") && g_vm.count("hitrate"))) {
c7c8bdee
PL
281 usage(desc);
282 return EXIT_FAILURE;
1fe98901
PL
283 }
284
c7c8bdee
PL
285 float increment = 1.1;
286 try {
287 increment = g_vm["increment"].as<float>();
b5955f7a 288 }
c7c8bdee
PL
289 catch(...) {
290 }
291
292 bool wantRecursion = g_vm.count("want-recursion");
a0149ecc 293 bool useECSFromFile = g_vm.count("ecs-from-file");
325e59e7 294 g_quiet = g_vm.count("quiet");
32823984 295
0a76eab7
PL
296 double hitrate = g_vm["hitrate"].as<double>();
297 if (hitrate > 100 || hitrate < 0) {
298 cerr<<"hitrate must be between 0 and 100, not "<<hitrate<<endl;
299 return EXIT_FAILURE;
300 }
301 hitrate /= 100;
d9ea45fc 302 uint32_t qpsstart = g_vm["initial-qps"].as<uint32_t>();
1fe98901 303
f3f8331c
RG
304 uint32_t maximumQps = std::numeric_limits<uint32_t>::max();
305 if (g_vm.count("maximum-qps")) {
306 maximumQps = g_vm["maximum-qps"].as<uint32_t>();
307 }
308
ce39630b
RG
309 double minimumSuccessRate = g_vm["minimum-success-rate"].as<double>();
310 if (minimumSuccessRate > 100.0 || minimumSuccessRate < 0.0) {
311 cerr<<"Minimum success rate must be between 0 and 100, not "<<minimumSuccessRate<<endl;
312 return EXIT_FAILURE;
313 }
314
a7f7d29f
RG
315 Netmask ecsRange;
316 if (g_vm.count("ecs")) {
317 dns_random_init("0123456789abcdef");
318
319 try {
320 ecsRange = Netmask(g_vm["ecs"].as<string>());
321 if (!ecsRange.empty()) {
322
323 if (!ecsRange.isIpv4()) {
324 cerr<<"Only IPv4 ranges are supported for ECS at the moment!"<<endl;
325 return EXIT_FAILURE;
326 }
327
325e59e7 328 if (!g_quiet) {
23165000
RG
329 cout<<"Adding ECS option to outgoing queries with random addresses from the "<<ecsRange.toString()<<" range"<<endl;
330 }
a7f7d29f
RG
331 }
332 }
333 catch (const NetmaskException& e) {
334 cerr<<"Error while parsing the ECS netmask: "<<e.reason<<endl;
335 return EXIT_FAILURE;
336 }
337 }
338
c7c8bdee
PL
339 struct sched_param param;
340 param.sched_priority=99;
1fe98901 341
ce39630b 342 if(sched_setscheduler(0, SCHED_FIFO, &param) < 0) {
325e59e7
RG
343 if (!g_quiet) {
344 cerr<<"Unable to set SCHED_FIFO: "<<strerror(errno)<<endl;
345 }
ce39630b 346 }
e9a27786 347
c7c8bdee 348 ifstream ifs(g_vm["query-file"].as<string>());
7d8a5de7 349 string line;
350 reportAllTypes();
fa5958af 351 vector<std::shared_ptr<vector<uint8_t> > > unknown, known;
9a0a974e
RG
352 std::vector<std::string> fields;
353 fields.reserve(3);
354
7d8a5de7 355 while(getline(ifs, line)) {
356 vector<uint8_t> packet;
a7f7d29f 357 DNSPacketWriter::optvect_t ednsOptions;
7d8a5de7 358 boost::trim(line);
46a3f566
RG
359 if (line.empty() || line.at(0) == '#') {
360 continue;
361 }
a0149ecc 362
9a0a974e
RG
363 fields.clear();
364 stringtok(fields, line, "\t ");
365 if ((useECSFromFile && fields.size() < 3) || fields.size() < 2) {
366 cerr<<"Skipping invalid line '"<<line<<", it does not contain enough values"<<endl;
367 continue;
368 }
369
370 const std::string& qname = fields.at(0);
371 const std::string& qtype = fields.at(1);
a0149ecc
RG
372 std::string subnet;
373
9a0a974e
RG
374 if (useECSFromFile) {
375 subnet = fields.at(2);
a0149ecc
RG
376 }
377
378 DNSPacketWriter pw(packet, DNSName(qname), DNSRecordContent::TypeToNumber(qtype));
1fe98901 379 pw.getHeader()->rd=wantRecursion;
b51ef4f9 380 pw.getHeader()->id=dns_random(UINT16_MAX);
a7f7d29f 381
a0149ecc 382 if(!subnet.empty() || !ecsRange.empty()) {
a7f7d29f 383 EDNSSubnetOpts opt;
a0149ecc 384 opt.source = Netmask(subnet.empty() ? "0.0.0.0/32" : subnet);
a7f7d29f
RG
385 ednsOptions.push_back(std::make_pair(EDNSOptionCode::ECS, makeEDNSSubnetOptsString(opt)));
386 }
387
388 if(!ednsOptions.empty() || pw.getHeader()->id % 2) {
389 pw.addOpt(1500, 0, EDNSOpts::DNSSECOK, ednsOptions);
390 pw.commit();
3df2e6c4 391 }
fa5958af 392 unknown.emplace_back(std::make_shared<vector<uint8_t>>(packet));
7d8a5de7 393 }
fa5958af 394 random_shuffle(unknown.begin(), unknown.end());
325e59e7 395 if (!g_quiet) {
23165000
RG
396 cout<<"Generated "<<unknown.size()<<" ready to use queries"<<endl;
397 }
7d8a5de7 398
e9a27786 399 vector<Socket*> sockets;
b807ae7b
PL
400 ComboAddress dest;
401 try {
402 dest = ComboAddress(g_vm["destination"].as<string>(), 53);
403 }
404 catch (PDNSException &e) {
405 cerr<<e.reason<<endl;
406 return EXIT_FAILURE;
407 }
c1e527e3 408 for(int i=0; i < 24; ++i) {
7f363f60 409 Socket *sock = new Socket(dest.sin4.sin_family, SOCK_DGRAM);
c1e527e3 410 // sock->connect(dest);
e9a27786 411 setSocketSendBuffer(sock->getHandle(), 2000000);
412 setSocketReceiveBuffer(sock->getHandle(), 2000000);
413 sockets.push_back(sock);
c1e527e3 414 }
415 new thread(recvThread, &sockets);
f3f8331c 416 uint32_t qps;
7d8a5de7 417
4038b8d6
RG
418 ofstream plot;
419 if (g_vm.count("plot-file")) {
420 plot.open(g_vm["plot-file"].as<string>());
421 if (!plot) {
422 cerr<<"Error opening "<<g_vm["plot-file"].as<string>()<<" for writing: "<<strerror(errno)<<endl;
423 return EXIT_FAILURE;
424 }
425 }
426
ce39630b
RG
427 double bestQPS = 0.0;
428 double bestPerfectQPS = 0.0;
429
f3f8331c 430 for(qps=qpsstart;;) {
fa5958af 431 double seconds=1;
325e59e7 432 if (!g_quiet) {
23165000
RG
433 cout<<"Aiming at "<<qps<< "qps (RD="<<wantRecursion<<") for "<<seconds<<" seconds at cache hitrate "<<100.0*hitrate<<"%";
434 }
fa5958af 435 unsigned int misses=(1-hitrate)*qps*seconds;
436 unsigned int total=qps*seconds;
b4f5799b
RG
437 if (misses == 0) {
438 misses = 1;
439 }
325e59e7 440 if (!g_quiet) {
23165000
RG
441 cout<<", need "<<misses<<" misses, "<<total<<" queries, have "<<unknown.size()<<" unknown left!"<<endl;
442 }
fa5958af 443
b4f5799b
RG
444 if (misses > unknown.size()) {
445 cerr<<"Not enough queries remaining (need at least "<<misses<<" and got "<<unknown.size()<<", please add more to the query file), exiting."<<endl;
ce39630b 446 return EXIT_FAILURE;
b4f5799b 447 }
fa5958af 448 vector<vector<uint8_t>*> toSend;
449 unsigned int n;
450 for(n=0; n < misses; ++n) {
451 auto ptr=unknown.back();
452 unknown.pop_back();
453 toSend.push_back(ptr.get());
454 known.push_back(ptr);
455 }
456 for(;n < total; ++n) {
b51ef4f9 457 toSend.push_back(known[dns_random(known.size())].get());
fa5958af 458 }
459 random_shuffle(toSend.begin(), toSend.end());
e9a27786 460 g_recvcounter.store(0);
3df2e6c4 461 g_recvbytes=0;
e9a27786 462 DTime dt;
463 dt.set();
464
a7f7d29f 465 sendPackets(&sockets, toSend, qps, dest, ecsRange);
e9a27786 466
ce39630b
RG
467 const auto udiff = dt.udiffNoReset();
468 const auto realqps=toSend.size()/(udiff/1000000.0);
325e59e7 469 if (!g_quiet) {
23165000
RG
470 cout<<"Achieved "<<realqps<<" qps over "<< udiff/1000000.0<<" seconds"<<endl;
471 }
e9a27786 472
473 usleep(50000);
ce39630b
RG
474 const auto received = g_recvcounter.load();
475 const auto udiffReceived = dt.udiff();
476 const auto realReceivedQPS = received/(udiffReceived/1000000.0);
477 double perc=received*100.0/toSend.size();
325e59e7 478 if (!g_quiet) {
23165000
RG
479 cout<<"Received "<<received<<" packets over "<< udiffReceived/1000000.0<<" seconds ("<<perc<<"%, adjusted received rate "<<realReceivedQPS<<" qps)"<<endl;
480 }
4038b8d6
RG
481
482 if (plot) {
ce39630b 483 plot<<qps<<" "<<realqps<<" "<<perc<<" "<<received/(udiff/1000000.0)<<" " << 8*g_recvbytes.load()/(udiff/1000000.0)<<endl;
4038b8d6
RG
484 plot.flush();
485 }
486
f3f8331c
RG
487 if (qps < maximumQps) {
488 qps *= increment;
489 }
ce39630b
RG
490 else {
491 qps = maximumQps;
492 }
493
494 if (minimumSuccessRate > 0.0 && perc < minimumSuccessRate) {
325e59e7 495 if (g_quiet) {
23165000
RG
496 cout<<bestQPS<<endl;
497 }
498 else {
499 cout<<"The latest success rate ("<<perc<<") dropped below the minimum success rate of "<<minimumSuccessRate<<", stopping."<<endl;
500 cout<<"The final rate reached before failing was "<<bestQPS<<" qps (best rate at 100% was "<<bestPerfectQPS<<" qps)"<<endl;
501 }
ce39630b
RG
502 break;
503 }
504
505 bestQPS = std::max(bestQPS, realReceivedQPS);
506 if (perc >= 100.0) {
507 bestPerfectQPS = std::max(bestPerfectQPS, realReceivedQPS);
508 }
7d8a5de7 509 }
4038b8d6
RG
510
511 if (plot) {
512 plot.flush();
513 }
514
e9a27786 515 // t1.detach();
7d8a5de7 516}
517 catch(std::exception& e)
518{
519 cerr<<"Fatal error: "<<e.what()<<endl;
325e59e7 520 return EXIT_FAILURE;
7d8a5de7 521}