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