Kea 1.5.0
command_options.cc
Go to the documentation of this file.
1// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include "command_options.h"
11#include <dhcp/iface_mgr.h>
12#include <dhcp/duid.h>
13#include <dhcp/option.h>
15#include <util/encode/hex.h>
16
17#include <boost/lexical_cast.hpp>
18#include <boost/date_time/posix_time/posix_time.hpp>
19
20#include <sstream>
21#include <stdio.h>
22#include <stdlib.h>
23#include <stdint.h>
24#include <unistd.h>
25#include <fstream>
26
27#ifdef HAVE_OPTRESET
28extern int optreset;
29#endif
30
31using namespace std;
32using namespace isc;
33using namespace isc::dhcp;
34
35namespace isc {
36namespace perfdhcp {
37
38// Refer to config_report so it will be embedded in the binary
40
42 : type_(ADDRESS) {
43}
44
46 : type_(lease_type) {
47}
48
49bool
50CommandOptions::LeaseType::is(const Type lease_type) const {
51 return (lease_type == type_);
52}
53
54bool
56 return (is(ADDRESS_AND_PREFIX) || (lease_type == type_));
57}
58
59void
61 type_ = lease_type;
62}
63
64void
65CommandOptions::LeaseType::fromCommandLine(const std::string& cmd_line_arg) {
66 if (cmd_line_arg == "address-only") {
67 type_ = ADDRESS;
68
69 } else if (cmd_line_arg == "prefix-only") {
70 type_ = PREFIX;
71
72 } else if (cmd_line_arg == "address-and-prefix") {
73 type_ = ADDRESS_AND_PREFIX;
74
75 } else {
76 isc_throw(isc::InvalidParameter, "value of lease-type: -e<lease-type>,"
77 " must be one of the following: 'address-only' or"
78 " 'prefix-only'");
79 }
80}
81
82std::string
84 switch (type_) {
85 case ADDRESS:
86 return ("address-only (IA_NA option added to the client's request)");
87 case PREFIX:
88 return ("prefix-only (IA_PD option added to the client's request)");
89 case ADDRESS_AND_PREFIX:
90 return ("address-and-prefix (Both IA_NA and IA_PD options added to the"
91 " client's request)");
92 default:
93 isc_throw(Unexpected, "internal error: undefined lease type code when"
94 " returning textual representation of the lease type");
95 }
96}
97
100 static CommandOptions options;
101 return (options);
102}
103
104void
106 // Default mac address used in DHCP messages
107 // if -b mac=<mac-address> was not specified
108 uint8_t mac[6] = { 0x0, 0xC, 0x1, 0x2, 0x3, 0x4 };
109
110 // Default packet drop time if -D<drop-time> parameter
111 // was not specified
112 double dt[2] = { 1., 1. };
113
114 // We don't use constructor initialization list because we
115 // will need to reset all members many times to perform unit tests
116 ipversion_ = 0;
117 exchange_mode_ = DORA_SARR;
118 lease_type_.set(LeaseType::ADDRESS);
119 rate_ = 0;
120 renew_rate_ = 0;
121 release_rate_ = 0;
122 report_delay_ = 0;
123 clients_num_ = 0;
124 mac_template_.assign(mac, mac + 6);
125 duid_template_.clear();
126 base_.clear();
127 mac_list_file_.clear();
128 mac_list_.clear();
129 num_request_.clear();
130 exit_wait_time_ = 0;
131 period_ = 0;
132 drop_time_set_ = 0;
133 drop_time_.assign(dt, dt + 2);
134 max_drop_.clear();
135 max_pdrop_.clear();
136 localname_.clear();
137 is_interface_ = false;
138 preload_ = 0;
139 aggressivity_ = 1;
140 local_port_ = 0;
141 seeded_ = false;
142 seed_ = 0;
143 broadcast_ = false;
144 rapid_commit_ = false;
145 use_first_ = false;
146 template_file_.clear();
147 rnd_offset_.clear();
148 xid_offset_.clear();
149 elp_offset_ = -1;
150 sid_offset_ = -1;
151 rip_offset_ = -1;
152 diags_.clear();
153 wrapped_.clear();
154 server_name_.clear();
155 v6_relay_encapsulation_level_ = 0;
156 generateDuidTemplate();
157 extra_opts_.clear();
158}
159
160bool
161CommandOptions::parse(int argc, char** const argv, bool print_cmd_line) {
162 // Reset internal variables used by getopt
163 // to eliminate undefined behavior when
164 // parsing different command lines multiple times
165
166#ifdef __GLIBC__
167 // Warning: non-portable code. This is due to a bug in glibc's
168 // getopt() which keeps internal state about an old argument vector
169 // (argc, argv) from last call and tries to scan them when a new
170 // argument vector (argc, argv) is passed. As the old vector may not
171 // be main()'s arguments, but heap allocated and may have been freed
172 // since, this becomes a use after free and results in random
173 // behavior. According to the NOTES section in glibc getopt()'s
174 // manpage, setting optind=0 resets getopt()'s state. Though this is
175 // not required in our usage of getopt(), the bug still happens
176 // unless we set optind=0.
177 //
178 // Setting optind=0 is non-portable code.
179 optind = 0;
180#else
181 optind = 1;
182#endif
183
184 // optreset is declared on BSD systems and is used to reset internal
185 // state of getopt(). When parsing command line arguments multiple
186 // times with getopt() the optreset must be set to 1 every time before
187 // parsing starts. Failing to do so will result in random behavior of
188 // getopt().
189#ifdef HAVE_OPTRESET
190 optreset = 1;
191#endif
192
193 opterr = 0;
194
195 // Reset values of class members
196 reset();
197
198 // Informs if program has been run with 'h' or 'v' option.
199 bool help_or_version_mode = initialize(argc, argv, print_cmd_line);
200 if (!help_or_version_mode) {
201 validate();
202 }
203 return (help_or_version_mode);
204}
205
206bool
207CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
208 int opt = 0; // Subsequent options returned by getopt()
209 std::string drop_arg; // Value of -D<value>argument
210 size_t percent_loc = 0; // Location of % sign in -D<value>
211 double drop_percent = 0; // % value (1..100) in -D<value%>
212 int num_drops = 0; // Max number of drops specified in -D<value>
213 int num_req = 0; // Max number of dropped
214 // requests in -n<max-drops>
215 int offset_arg = 0; // Temporary variable holding offset arguments
216 std::string sarg; // Temporary variable for string args
217
218 std::ostringstream stream;
219 stream << "perfdhcp";
220 int num_mac_list_files = 0;
221
222 // In this section we collect argument values from command line
223 // they will be tuned and validated elsewhere
224 while((opt = getopt(argc, argv, "hv46A:r:t:R:b:n:p:d:D:l:P:a:L:M:"
225 "s:iBc1T:X:O:o:E:S:I:x:W:w:e:f:F:")) != -1) {
226 stream << " -" << static_cast<char>(opt);
227 if (optarg) {
228 stream << " " << optarg;
229 }
230 switch (opt) {
231 case '1':
232 use_first_ = true;
233 break;
234
235 // Simulate DHCPv6 relayed traffic.
236 case 'A':
237 // @todo: At the moment we only support simulating a single relay
238 // agent. In the future we should extend it to up to 32.
239 // See comment in https://github.com/isc-projects/kea/pull/22#issuecomment-243405600
240 v6_relay_encapsulation_level_ =
241 static_cast<uint8_t>(positiveInteger("-A<encapsulation-level> must"
242 " be a positive integer"));
243 if (v6_relay_encapsulation_level_ != 1) {
244 isc_throw(isc::InvalidParameter, "-A only supports 1 at the moment.");
245 }
246 break;
247
248 case '4':
249 check(ipversion_ == 6, "IP version already set to 6");
250 ipversion_ = 4;
251 break;
252
253 case '6':
254 check(ipversion_ == 4, "IP version already set to 4");
255 ipversion_ = 6;
256 break;
257
258 case 'a':
259 aggressivity_ = positiveInteger("value of aggressivity: -a<value>"
260 " must be a positive integer");
261 break;
262
263 case 'b':
264 check(base_.size() > 3, "-b<value> already specified,"
265 " unexpected occurrence of 5th -b<value>");
266 base_.push_back(optarg);
267 decodeBase(base_.back());
268 break;
269
270 case 'B':
271 broadcast_ = true;
272 break;
273
274 case 'c':
275 rapid_commit_ = true;
276 break;
277
278 case 'd':
279 check(drop_time_set_ > 1,
280 "maximum number of drops already specified, "
281 "unexpected 3rd occurrence of -d<value>");
282 try {
283 drop_time_[drop_time_set_] =
284 boost::lexical_cast<double>(optarg);
285 } catch (boost::bad_lexical_cast&) {
287 "value of drop time: -d<value>"
288 " must be positive number");
289 }
290 check(drop_time_[drop_time_set_] <= 0.,
291 "drop-time must be a positive number");
292 drop_time_set_ = true;
293 break;
294
295 case 'D':
296 drop_arg = std::string(optarg);
297 percent_loc = drop_arg.find('%');
298 check(max_pdrop_.size() > 1 || max_drop_.size() > 1,
299 "values of maximum drops: -D<value> already "
300 "specified, unexpected 3rd occurrence of -D<value>");
301 if ((percent_loc) != std::string::npos) {
302 try {
303 drop_percent =
304 boost::lexical_cast<double>(drop_arg.substr(0, percent_loc));
305 } catch (boost::bad_lexical_cast&) {
307 "value of drop percentage: -D<value%>"
308 " must be 0..100");
309 }
310 check((drop_percent <= 0) || (drop_percent >= 100),
311 "value of drop percentage: -D<value%> must be 0..100");
312 max_pdrop_.push_back(drop_percent);
313 } else {
314 num_drops = positiveInteger("value of max drops number:"
315 " -D<value> must be a positive integer");
316 max_drop_.push_back(num_drops);
317 }
318 break;
319
320 case 'e':
321 initLeaseType();
322 break;
323
324 case 'E':
325 elp_offset_ = nonNegativeInteger("value of time-offset: -E<value>"
326 " must not be a negative integer");
327 break;
328
329 case 'f':
330 renew_rate_ = positiveInteger("value of the renew rate: -f<renew-rate>"
331 " must be a positive integer");
332 break;
333
334 case 'F':
335 release_rate_ = positiveInteger("value of the release rate:"
336 " -F<release-rate> must be a"
337 " positive integer");
338 break;
339
340 case 'h':
341 usage();
342 return (true);
343
344 case 'i':
345 exchange_mode_ = DO_SA;
346 break;
347
348 case 'I':
349 rip_offset_ = positiveInteger("value of ip address offset:"
350 " -I<value> must be a"
351 " positive integer");
352 break;
353
354 case 'l':
355 localname_ = std::string(optarg);
356 initIsInterface();
357 break;
358
359 case 'L':
360 local_port_ = nonNegativeInteger("value of local port:"
361 " -L<value> must not be a"
362 " negative integer");
363 check(local_port_ >
364 static_cast<int>(std::numeric_limits<uint16_t>::max()),
365 "local-port must be lower than " +
366 boost::lexical_cast<std::string>(std::numeric_limits<uint16_t>::max()));
367 break;
368
369 case 'M':
370 check(num_mac_list_files >= 1, "only -M option can be specified");
371 num_mac_list_files++;
372 mac_list_file_ = std::string(optarg);
373 loadMacs();
374 break;
375
376 case 'W':
377 exit_wait_time_ = nonNegativeInteger("value of exist wait time: "
378 "-W<value> must not be a "
379 "negative integer");
380 break;
381
382 case 'n':
383 num_req = positiveInteger("value of num-request:"
384 " -n<value> must be a positive integer");
385 if (num_request_.size() >= 2) {
387 "value of maximum number of requests: -n<value> "
388 "already specified, unexpected 3rd occurrence"
389 " of -n<value>");
390 }
391 num_request_.push_back(num_req);
392 break;
393
394 case 'O':
395 if (rnd_offset_.size() < 2) {
396 offset_arg = positiveInteger("value of random offset: "
397 "-O<value> must be greater than 3");
398 } else {
400 "random offsets already specified,"
401 " unexpected 3rd occurrence of -O<value>");
402 }
403 check(offset_arg < 3, "value of random random-offset:"
404 " -O<value> must be greater than 3 ");
405 rnd_offset_.push_back(offset_arg);
406 break;
407 case 'o': {
408
409 // we must know how to contruct the option: whether it's v4 or v6.
410 check( (ipversion_ != 4) && (ipversion_ != 6),
411 "-4 or -6 must be explicitly specified before -o is used.");
412
413 // custom option (expected format: code,hexstring)
414 std::string opt_text = std::string(optarg);
415 size_t coma_loc = opt_text.find(',');
416 check(coma_loc == std::string::npos,
417 "-o option must provide option code, a coma and hexstring for"
418 " the option content, e.g. -o60,646f63736973 for sending option"
419 " 60 (class-id) with the value 'docsis'");
420 int code = 0;
421
422 // Try to parse the option code
423 try {
424 code = boost::lexical_cast<int>(opt_text.substr(0,coma_loc));
425 check(code <= 0, "Option code can't be negative");
426 } catch (boost::bad_lexical_cast&) {
427 isc_throw(InvalidParameter, "Invalid option code specified for "
428 "-o option, expected format: -o<integer>,<hexstring>");
429 }
430
431 // Now try to interpret the hexstring
432 opt_text = opt_text.substr(coma_loc + 1);
433 std::vector<uint8_t> bin;
434 try {
435 isc::util::encode::decodeHex(opt_text, bin);
436 } catch (BadValue& e) {
437 isc_throw(InvalidParameter, "Error during encoding option -o:"
438 << e.what());
439 }
440
441 // Create and remember the option.
442 OptionPtr opt(new Option(ipversion_ == 4 ? Option::V4 : Option::V6,
443 code, bin));
444 extra_opts_.insert(make_pair(code, opt));
445 break;
446 }
447 case 'p':
448 period_ = positiveInteger("value of test period:"
449 " -p<value> must be a positive integer");
450 break;
451
452 case 'P':
453 preload_ = nonNegativeInteger("number of preload packets:"
454 " -P<value> must not be "
455 "a negative integer");
456 break;
457
458 case 'r':
459 rate_ = positiveInteger("value of rate:"
460 " -r<value> must be a positive integer");
461 break;
462
463 case 'R':
464 initClientsNum();
465 break;
466
467 case 's':
468 seed_ = static_cast<unsigned int>
469 (nonNegativeInteger("value of seed:"
470 " -s <seed> must be non-negative integer"));
471 seeded_ = seed_ > 0 ? true : false;
472 break;
473
474 case 'S':
475 sid_offset_ = positiveInteger("value of server id offset:"
476 " -S<value> must be a"
477 " positive integer");
478 break;
479
480 case 't':
481 report_delay_ = positiveInteger("value of report delay:"
482 " -t<value> must be a"
483 " positive integer");
484 break;
485
486 case 'T':
487 if (template_file_.size() < 2) {
488 sarg = nonEmptyString("template file name not specified,"
489 " expected -T<filename>");
490 template_file_.push_back(sarg);
491 } else {
493 "template files are already specified,"
494 " unexpected 3rd -T<filename> occurrence");
495 }
496 break;
497
498 case 'v':
499 version();
500 return (true);
501
502 case 'w':
503 wrapped_ = nonEmptyString("command for wrapped mode:"
504 " -w<command> must be specified");
505 break;
506
507 case 'x':
508 diags_ = nonEmptyString("value of diagnostics selectors:"
509 " -x<value> must be specified");
510 break;
511
512 case 'X':
513 if (xid_offset_.size() < 2) {
514 offset_arg = positiveInteger("value of transaction id:"
515 " -X<value> must be a"
516 " positive integer");
517 } else {
519 "transaction ids already specified,"
520 " unexpected 3rd -X<value> occurrence");
521 }
522 xid_offset_.push_back(offset_arg);
523 break;
524
525 default:
526 isc_throw(isc::InvalidParameter, "unknown command line option");
527 }
528 }
529
530 // If the IP version was not specified in the
531 // command line, assume IPv4.
532 if (ipversion_ == 0) {
533 ipversion_ = 4;
534 }
535
536 // If template packet files specified for both DISCOVER/SOLICIT
537 // and REQUEST/REPLY exchanges make sure we have transaction id
538 // and random duid offsets for both exchanges. We will duplicate
539 // value specified as -X<value> and -R<value> for second
540 // exchange if user did not specified otherwise.
541 if (template_file_.size() > 1) {
542 if (xid_offset_.size() == 1) {
543 xid_offset_.push_back(xid_offset_[0]);
544 }
545 if (rnd_offset_.size() == 1) {
546 rnd_offset_.push_back(rnd_offset_[0]);
547 }
548 }
549
550 // Get server argument
551 // NoteFF02::1:2 and FF02::1:3 are defined in RFC 8415 as
552 // All_DHCP_Relay_Agents_and_Servers and All_DHCP_Servers
553 // addresses
554 check(optind < argc -1, "extra arguments?");
555 if (optind == argc - 1) {
556 server_name_ = argv[optind];
557 stream << " " << server_name_;
558 // Decode special cases
559 if ((ipversion_ == 4) && (server_name_.compare("all") == 0)) {
560 broadcast_ = true;
561 // Use broadcast address as server name.
562 server_name_ = DHCP_IPV4_BROADCAST_ADDRESS;
563 } else if ((ipversion_ == 6) && (server_name_.compare("all") == 0)) {
565 } else if ((ipversion_ == 6) &&
566 (server_name_.compare("servers") == 0)) {
567 server_name_ = ALL_DHCP_SERVERS;
568 }
569 }
570
571 if (print_cmd_line) {
572 std::cout << "Running: " << stream.str() << std::endl;
573 }
574
575 // Handle the local '-l' address/interface
576 if (!localname_.empty()) {
577 if (server_name_.empty()) {
578 if (is_interface_ && (ipversion_ == 4)) {
579 broadcast_ = true;
580 server_name_ = DHCP_IPV4_BROADCAST_ADDRESS;
581 } else if (is_interface_ && (ipversion_ == 6)) {
583 }
584 }
585 }
586 if (server_name_.empty()) {
588 "without an interface, server is required");
589 }
590
591 // If DUID is not specified from command line we need to
592 // generate one.
593 if (duid_template_.empty()) {
594 generateDuidTemplate();
595 }
596 return (false);
597}
598
599void
600CommandOptions::initClientsNum() {
601 const std::string errmsg =
602 "value of -R <value> must be non-negative integer";
603
604 try {
605 // Declare clients_num as as 64-bit signed value to
606 // be able to detect negative values provided
607 // by user. We would not detect negative values
608 // if we casted directly to unsigned value.
609 long long clients_num = boost::lexical_cast<long long>(optarg);
610 check(clients_num < 0, errmsg);
611 clients_num_ = boost::lexical_cast<uint32_t>(optarg);
612 } catch (boost::bad_lexical_cast&) {
614 }
615}
616
617void
618CommandOptions::initIsInterface() {
619 is_interface_ = false;
620 if (!localname_.empty()) {
622 if (iface_mgr.getIface(localname_) != NULL) {
623 is_interface_ = true;
624 }
625 }
626}
627
628void
629CommandOptions::decodeBase(const std::string& base) {
630 std::string b(base);
631 boost::algorithm::to_lower(b);
632
633 // Currently we only support mac and duid
634 if ((b.substr(0, 4) == "mac=") || (b.substr(0, 6) == "ether=")) {
635 decodeMacBase(b);
636 } else if (b.substr(0, 5) == "duid=") {
637 decodeDuid(b);
638 } else {
640 "base value not provided as -b<value>,"
641 " expected -b mac=<mac> or -b duid=<duid>");
642 }
643}
644
645void
646CommandOptions::decodeMacBase(const std::string& base) {
647 // Strip string from mac=
648 size_t found = base.find('=');
649 static const char* errmsg = "expected -b<base> format for"
650 " mac address is -b mac=00::0C::01::02::03::04 or"
651 " -b mac=00:0C:01:02:03:04";
652 check(found == std::string::npos, errmsg);
653
654 // Decode mac address to vector of uint8_t
655 std::istringstream s1(base.substr(found + 1));
656 std::string token;
657 mac_template_.clear();
658 // Get pieces of MAC address separated with : (or even ::)
659 while (std::getline(s1, token, ':')) {
660 // Convert token to byte value using std::istringstream
661 if (token.length() > 0) {
662 unsigned int ui = 0;
663 try {
664 // Do actual conversion
665 ui = convertHexString(token);
666 } catch (isc::InvalidParameter&) {
668 "invalid characters in MAC provided");
669
670 }
671 // If conversion succeeded store byte value
672 mac_template_.push_back(ui);
673 }
674 }
675 // MAC address must consist of 6 octets, otherwise it is invalid
676 check(mac_template_.size() != 6, errmsg);
677}
678
679void
680CommandOptions::decodeDuid(const std::string& base) {
681 // Strip argument from duid=
682 std::vector<uint8_t> duid_template;
683 size_t found = base.find('=');
684 check(found == std::string::npos, "expected -b<base>"
685 " format for duid is -b duid=<duid>");
686 std::string b = base.substr(found + 1);
687
688 // DUID must have even number of digits and must not be longer than 64 bytes
689 check(b.length() & 1, "odd number of hexadecimal digits in duid");
690 check(b.length() > 128, "duid too large");
691 check(b.length() == 0, "no duid specified");
692
693 // Turn pairs of hexadecimal digits into vector of octets
694 for (size_t i = 0; i < b.length(); i += 2) {
695 unsigned int ui = 0;
696 try {
697 // Do actual conversion
698 ui = convertHexString(b.substr(i, 2));
699 } catch (isc::InvalidParameter&) {
701 "invalid characters in DUID provided,"
702 " expected hex digits");
703 }
704 duid_template.push_back(static_cast<uint8_t>(ui));
705 }
706 // @todo Get rid of this limitation when we manage add support
707 // for DUIDs other than LLT. Shorter DUIDs may be useful for
708 // server testing purposes.
709 check(duid_template.size() < 6, "DUID must be at least 6 octets long");
710 // Assign the new duid only if successfully generated.
711 std::swap(duid_template, duid_template_);
712}
713
714void
715CommandOptions::generateDuidTemplate() {
716 using namespace boost::posix_time;
717 // Duid template will be most likely generated only once but
718 // it is ok if it is called more then once so we simply
719 // regenerate it and discard previous value.
720 duid_template_.clear();
721 const uint8_t duid_template_len = 14;
722 duid_template_.resize(duid_template_len);
723 // The first four octets consist of DUID LLT and hardware type.
724 duid_template_[0] = static_cast<uint8_t>(static_cast<uint16_t>(isc::dhcp::DUID::DUID_LLT) >> 8);
725 duid_template_[1] = static_cast<uint8_t>(static_cast<uint16_t>(isc::dhcp::DUID::DUID_LLT) & 0xff);
726 duid_template_[2] = HWTYPE_ETHERNET >> 8;
727 duid_template_[3] = HWTYPE_ETHERNET & 0xff;
728
729 // As described in RFC 8415: 'the time value is the time
730 // that the DUID is generated represented in seconds
731 // since midnight (UTC), January 1, 2000, modulo 2^32.'
732 ptime now = microsec_clock::universal_time();
733 ptime duid_epoch(from_iso_string("20000101T000000"));
734 time_period period(duid_epoch, now);
735 uint32_t duration_sec = htonl(period.length().total_seconds());
736 memcpy(&duid_template_[4], &duration_sec, 4);
737
738 // Set link layer address (6 octets). This value may be
739 // randomized before sending a packet to simulate different
740 // clients.
741 memcpy(&duid_template_[8], &mac_template_[0], 6);
742}
743
744uint8_t
745CommandOptions::convertHexString(const std::string& text) const {
746 unsigned int ui = 0;
747 // First, check if we are dealing with hexadecimal digits only
748 for (size_t i = 0; i < text.length(); ++i) {
749 if (!std::isxdigit(text[i])) {
751 "The following digit: " << text[i] << " in "
752 << text << "is not hexadecimal");
753 }
754 }
755 // If we are here, we have valid string to convert to octet
756 std::istringstream text_stream(text);
757 text_stream >> std::hex >> ui >> std::dec;
758 // Check if for some reason we have overflow - this should never happen!
759 if (ui > 0xFF) {
760 isc_throw(isc::InvalidParameter, "Can't convert more than"
761 " two hex digits to byte");
762 }
763 return ui;
764}
765
766void CommandOptions::loadMacs() {
767 std::string line;
768 std::ifstream infile(mac_list_file_.c_str());
769 size_t cnt = 0;
770 while (std::getline(infile, line)) {
771 cnt++;
772 stringstream tmp;
773 tmp << "invalid mac in input line " << cnt;
774 // Let's print more meaningful error that contains line with error.
775 check(decodeMacString(line), tmp.str());
776 }
777}
778
779bool CommandOptions::decodeMacString(const std::string& line) {
780 // decode mac string into a vector of uint8_t returns true in case of error.
781 std::istringstream s(line);
782 std::string token;
783 std::vector<uint8_t> mac;
784 while(std::getline(s, token, ':')) {
785 // Convert token to byte value using std::istringstream
786 if (token.length() > 0) {
787 unsigned int ui = 0;
788 try {
789 // Do actual conversion
790 ui = convertHexString(token);
791 } catch (isc::InvalidParameter&) {
792 return (true);
793 }
794 // If conversion succeeded store byte value
795 mac.push_back(ui);
796 }
797 }
798 mac_list_.push_back(mac);
799 return (false);
800}
801
802void
803CommandOptions::validate() const {
804 check((getIpVersion() != 4) && (isBroadcast() != 0),
805 "-B is not compatible with IPv6 (-6)");
806 check((getIpVersion() != 6) && (isRapidCommit() != 0),
807 "-6 (IPv6) must be set to use -c");
808 check(getIpVersion() == 4 && isUseRelayedV6(),
809 "Can't use -4 with -A, it's a V6 only option.");
810 check((getIpVersion() != 6) && (getReleaseRate() != 0),
811 "-F<release-rate> may be used with -6 (IPv6) only");
812 check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1),
813 "second -n<num-request> is not compatible with -i");
814 check((getIpVersion() == 4) && !getLeaseType().is(LeaseType::ADDRESS),
815 "-6 option must be used if lease type other than '-e address-only'"
816 " is specified");
817 check(!getTemplateFiles().empty() &&
819 "template files may be only used with '-e address-only'");
820 check((getExchangeMode() == DO_SA) && (getDropTime()[1] != 1.),
821 "second -d<drop-time> is not compatible with -i");
822 check((getExchangeMode() == DO_SA) &&
823 ((getMaxDrop().size() > 1) || (getMaxDropPercentage().size() > 1)),
824 "second -D<max-drop> is not compatible with -i");
825 check((getExchangeMode() == DO_SA) && (isUseFirst()),
826 "-1 is not compatible with -i");
827 check((getExchangeMode() == DO_SA) && (getTemplateFiles().size() > 1),
828 "second -T<template-file> is not compatible with -i");
829 check((getExchangeMode() == DO_SA) && (getTransactionIdOffset().size() > 1),
830 "second -X<xid-offset> is not compatible with -i");
831 check((getExchangeMode() == DO_SA) && (getRandomOffset().size() > 1),
832 "second -O<random-offset is not compatible with -i");
833 check((getExchangeMode() == DO_SA) && (getElapsedTimeOffset() >= 0),
834 "-E<time-offset> is not compatible with -i");
835 check((getExchangeMode() == DO_SA) && (getServerIdOffset() >= 0),
836 "-S<srvid-offset> is not compatible with -i");
837 check((getExchangeMode() == DO_SA) && (getRequestedIpOffset() >= 0),
838 "-I<ip-offset> is not compatible with -i");
839 check((getExchangeMode() == DO_SA) && (getRenewRate() != 0),
840 "-f<renew-rate> is not compatible with -i");
841 check((getExchangeMode() == DO_SA) && (getReleaseRate() != 0),
842 "-F<release-rate> is not compatible with -i");
843 check((getExchangeMode() != DO_SA) && (isRapidCommit() != 0),
844 "-i must be set to use -c");
845 check((getRate() != 0) && (getRenewRate() + getReleaseRate() > getRate()),
846 "The sum of Renew rate (-f<renew-rate>) and Release rate"
847 " (-F<release-rate>) must not be greater than the exchange"
848 " rate specified as -r<rate>");
849 check((getRate() == 0) && (getRenewRate() != 0),
850 "Renew rate specified as -f<renew-rate> must not be specified"
851 " when -r<rate> parameter is not specified");
852 check((getRate() == 0) && (getReleaseRate() != 0),
853 "Release rate specified as -F<release-rate> must not be specified"
854 " when -r<rate> parameter is not specified");
855 check((getTemplateFiles().size() < getTransactionIdOffset().size()),
856 "-T<template-file> must be set to use -X<xid-offset>");
857 check((getTemplateFiles().size() < getRandomOffset().size()),
858 "-T<template-file> must be set to use -O<random-offset>");
859 check((getTemplateFiles().size() < 2) && (getElapsedTimeOffset() >= 0),
860 "second/request -T<template-file> must be set to use -E<time-offset>");
861 check((getTemplateFiles().size() < 2) && (getServerIdOffset() >= 0),
862 "second/request -T<template-file> must be set to "
863 "use -S<srvid-offset>");
864 check((getTemplateFiles().size() < 2) && (getRequestedIpOffset() >= 0),
865 "second/request -T<template-file> must be set to "
866 "use -I<ip-offset>");
867 check((!getMacListFile().empty() && base_.size() > 0),
868 "Can't use -b with -M option");
869}
870
871void
872CommandOptions::check(bool condition, const std::string& errmsg) const {
873 // The same could have been done with macro or just if statement but
874 // we prefer functions to macros here
875 std::ostringstream stream;
876 stream << errmsg << "\n";
877 if (condition) {
879 }
880}
881
882int
883CommandOptions::positiveInteger(const std::string& errmsg) const {
884 try {
885 int value = boost::lexical_cast<int>(optarg);
886 check(value <= 0, errmsg);
887 return (value);
888 } catch (boost::bad_lexical_cast&) {
890 }
891}
892
893int
894CommandOptions::nonNegativeInteger(const std::string& errmsg) const {
895 try {
896 int value = boost::lexical_cast<int>(optarg);
897 check(value < 0, errmsg);
898 return (value);
899 } catch (boost::bad_lexical_cast&) {
901 }
902}
903
904std::string
905CommandOptions::nonEmptyString(const std::string& errmsg) const {
906 std::string sarg = optarg;
907 if (sarg.length() == 0) {
909 }
910 return sarg;
911}
912
913void
914CommandOptions::initLeaseType() {
915 std::string lease_type_arg = optarg;
916 lease_type_.fromCommandLine(lease_type_arg);
917}
918
919void
921 std::cout << "IPv" << static_cast<int>(ipversion_) << std::endl;
922 if (exchange_mode_ == DO_SA) {
923 if (ipversion_ == 4) {
924 std::cout << "DISCOVER-OFFER only" << std::endl;
925 } else {
926 std::cout << "SOLICIT-ADVERTISE only" << std::endl;
927 }
928 }
929 std::cout << "lease-type=" << getLeaseType().toText() << std::endl;
930 if (rate_ != 0) {
931 std::cout << "rate[1/s]=" << rate_ << std::endl;
932 }
933 if (getRenewRate() != 0) {
934 std::cout << "renew-rate[1/s]=" << getRenewRate() << std::endl;
935 }
936 if (getReleaseRate() != 0) {
937 std::cout << "release-rate[1/s]=" << getReleaseRate() << std::endl;
938 }
939 if (report_delay_ != 0) {
940 std::cout << "report[s]=" << report_delay_ << std::endl;
941 }
942 if (clients_num_ != 0) {
943 std::cout << "clients=" << clients_num_ << std::endl;
944 }
945 for (size_t i = 0; i < base_.size(); ++i) {
946 std::cout << "base[" << i << "]=" << base_[i] << std::endl;
947 }
948 for (size_t i = 0; i < num_request_.size(); ++i) {
949 std::cout << "num-request[" << i << "]=" << num_request_[i] << std::endl;
950 }
951 if (period_ != 0) {
952 std::cout << "test-period=" << period_ << std::endl;
953 }
954 for (size_t i = 0; i < drop_time_.size(); ++i) {
955 std::cout << "drop-time[" << i << "]=" << drop_time_[i] << std::endl;
956 }
957 for (size_t i = 0; i < max_drop_.size(); ++i) {
958 std::cout << "max-drop{" << i << "]=" << max_drop_[i] << std::endl;
959 }
960 for (size_t i = 0; i < max_pdrop_.size(); ++i) {
961 std::cout << "max-pdrop{" << i << "]=" << max_pdrop_[i] << std::endl;
962 }
963 if (preload_ != 0) {
964 std::cout << "preload=" << preload_ << std::endl;
965 }
966 std::cout << "aggressivity=" << aggressivity_ << std::endl;
967 if (getLocalPort() != 0) {
968 std::cout << "local-port=" << local_port_ << std::endl;
969 }
970 if (seeded_) {
971 std::cout << "seed=" << seed_ << std::endl;
972 }
973 if (broadcast_) {
974 std::cout << "broadcast" << std::endl;
975 }
976 if (rapid_commit_) {
977 std::cout << "rapid-commit" << std::endl;
978 }
979 if (use_first_) {
980 std::cout << "use-first" << std::endl;
981 }
982 if (!mac_list_file_.empty()) {
983 std::cout << "mac-list-file=" << mac_list_file_ << std::endl;
984 }
985 for (size_t i = 0; i < template_file_.size(); ++i) {
986 std::cout << "template-file[" << i << "]=" << template_file_[i] << std::endl;
987 }
988 for (size_t i = 0; i < xid_offset_.size(); ++i) {
989 std::cout << "xid-offset[" << i << "]=" << xid_offset_[i] << std::endl;
990 }
991 if (elp_offset_ != 0) {
992 std::cout << "elp-offset=" << elp_offset_ << std::endl;
993 }
994 for (size_t i = 0; i < rnd_offset_.size(); ++i) {
995 std::cout << "rnd-offset[" << i << "]=" << rnd_offset_[i] << std::endl;
996 }
997 if (sid_offset_ != 0) {
998 std::cout << "sid-offset=" << sid_offset_ << std::endl;
999 }
1000 if (rip_offset_ != 0) {
1001 std::cout << "rip-offset=" << rip_offset_ << std::endl;
1002 }
1003 if (!diags_.empty()) {
1004 std::cout << "diagnostic-selectors=" << diags_ << std::endl;
1005 }
1006 if (!wrapped_.empty()) {
1007 std::cout << "wrapped=" << wrapped_ << std::endl;
1008 }
1009 if (!localname_.empty()) {
1010 if (is_interface_) {
1011 std::cout << "interface=" << localname_ << std::endl;
1012 } else {
1013 std::cout << "local-addr=" << localname_ << std::endl;
1014 }
1015 }
1016 if (!server_name_.empty()) {
1017 std::cout << "server=" << server_name_ << std::endl;
1018 }
1019}
1020
1021void
1023 std::cout <<
1024 "perfdhcp [-hv] [-4|-6] [-A<encapsulation-level>] [-e<lease-type>]"
1025 " [-r<rate>] [-f<renew-rate>]\n"
1026 " [-F<release-rate>] [-t<report>] [-R<range>] [-b<base>]\n"
1027 " [-n<num-request>] [-p<test-period>] [-d<drop-time>]\n"
1028 " [-D<max-drop>] [-l<local-addr|interface>] [-P<preload>]\n"
1029 " [-a<aggressivity>] [-L<local-port>] [-s<seed>] [-i] [-B]\n"
1030 " [-W<late-exit-delay>]\n"
1031 " [-c] [-1] [-M<mac-list-file>] [-T<template-file>]\n"
1032 " [-X<xid-offset>] [-O<random-offset] [-E<time-offset>]\n"
1033 " [-S<srvid-offset>] [-I<ip-offset>] [-x<diagnostic-selector>]\n"
1034 " [-w<wrapped>] [server]\n"
1035 "\n"
1036 "The [server] argument is the name/address of the DHCP server to\n"
1037 "contact. For DHCPv4 operation, exchanges are initiated by\n"
1038 "transmitting a DHCP DISCOVER to this address.\n"
1039 "\n"
1040 "For DHCPv6 operation, exchanges are initiated by transmitting a DHCP\n"
1041 "SOLICIT to this address. In the DHCPv6 case, the special name 'all'\n"
1042 "can be used to refer to All_DHCP_Relay_Agents_and_Servers (the\n"
1043 "multicast address FF02::1:2), or the special name 'servers' to refer\n"
1044 "to All_DHCP_Servers (the multicast address FF05::1:3). The [server]\n"
1045 "argument is optional only in the case that -l is used to specify an\n"
1046 "interface, in which case [server] defaults to 'all'.\n"
1047 "\n"
1048 "The default is to perform a single 4-way exchange, effectively pinging\n"
1049 "the server.\n"
1050 "The -r option is used to set up a performance test, without\n"
1051 "it exchanges are initiated as fast as possible.\n"
1052 "\n"
1053 "Options:\n"
1054 "-1: Take the server-ID option from the first received message.\n"
1055 "-4: DHCPv4 operation (default). This is incompatible with the -6 option.\n"
1056 "-6: DHCPv6 operation. This is incompatible with the -4 option.\n"
1057 "-a<aggressivity>: When the target sending rate is not yet reached,\n"
1058 " control how many exchanges are initiated before the next pause.\n"
1059 "-b<base>: The base mac, duid, IP, etc, used to simulate different\n"
1060 " clients. This can be specified multiple times, each instance is\n"
1061 " in the <type>=<value> form, for instance:\n"
1062 " (and default) mac=00:0c:01:02:03:04.\n"
1063 "-d<drop-time>: Specify the time after which a request is treated as\n"
1064 " having been lost. The value is given in seconds and may contain a\n"
1065 " fractional component. The default is 1 second.\n"
1066 "-e<lease-type>: A type of lease being requested from the server. It\n"
1067 " may be one of the following: address-only, prefix-only or\n"
1068 " address-and-prefix. The address-only indicates that the regular\n"
1069 " address (v4 or v6) will be requested. The prefix-only indicates\n"
1070 " that the IPv6 prefix will be requested. The address-and-prefix\n"
1071 " indicates that both IPv6 address and prefix will be requested.\n"
1072 " The '-e prefix-only' and -'e address-and-prefix' must not be\n"
1073 " used with -4.\n"
1074 "-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6)\n"
1075 " elapsed-time option in the (second/request) template.\n"
1076 " The value 0 disables it.\n"
1077 "-f<renew-rate>: Rate at which DHCPv4 or DHCPv6 renew requests are sent\n"
1078 " to a server. This value is only valid when used in conjunction\n"
1079 " with the exchange rate (given by -r<rate>). Furthermore the sum of\n"
1080 " this value and the release-rate (given by -F<rate) must be equal\n"
1081 " to or less than the exchange rate.\n"
1082 "-h: Print this help.\n"
1083 "-i: Do only the initial part of an exchange: DO or SA, depending on\n"
1084 " whether -6 is given.\n"
1085 "-I<ip-offset>: Offset of the (DHCPv4) IP address in the requested-IP\n"
1086 " option / (DHCPv6) IA_NA option in the (second/request) template.\n"
1087 "-l<local-addr|interface>: For DHCPv4 operation, specify the local\n"
1088 " hostname/address to use when communicating with the server. By\n"
1089 " default, the interface address through which traffic would\n"
1090 " normally be routed to the server is used.\n"
1091 " For DHCPv6 operation, specify the name of the network interface\n"
1092 " via which exchanges are initiated.\n"
1093 "-L<local-port>: Specify the local port to use\n"
1094 " (the value 0 means to use the default).\n"
1095 "-M<mac-list-file>: A text file containing a list of MAC addresses,\n"
1096 " one per line. If provided, a MAC address will be chosen randomly\n"
1097 " from this list for every new exchange. In the DHCPv6 case, MAC\n"
1098 " addresses are used to generate DUID-LLs. This parameter must not be\n"
1099 " used in conjunction with the -b parameter.\n"
1100 "-O<random-offset>: Offset of the last octet to randomize in the template.\n"
1101 "-P<preload>: Initiate first <preload> exchanges back to back at startup.\n"
1102 "-r<rate>: Initiate <rate> DORA/SARR (or if -i is given, DO/SA)\n"
1103 " exchanges per second. A periodic report is generated showing the\n"
1104 " number of exchanges which were not completed, as well as the\n"
1105 " average response latency. The program continues until\n"
1106 " interrupted, at which point a final report is generated.\n"
1107 "-R<range>: Specify how many different clients are used. With 1\n"
1108 " (the default), all requests seem to come from the same client.\n"
1109 "-s<seed>: Specify the seed for randomization, making it repeatable.\n"
1110 "-S<srvid-offset>: Offset of the server-ID option in the\n"
1111 " (second/request) template.\n"
1112 "-T<template-file>: The name of a file containing the template to use\n"
1113 " as a stream of hexadecimal digits.\n"
1114 "-v: Report the version number of this program.\n"
1115 "-W<time>: Specifies exit-wait-time parameter, that makes perfdhcp wait\n"
1116 " for <time> us after an exit condition has been met to receive all\n"
1117 " packets without sending any new packets. Expressed in microseconds.\n"
1118 "-w<wrapped>: Command to call with start/stop at the beginning/end of\n"
1119 " the program.\n"
1120 "-x<diagnostic-selector>: Include extended diagnostics in the output.\n"
1121 " <diagnostic-selector> is a string of single-keywords specifying\n"
1122 " the operations for which verbose output is desired. The selector\n"
1123 " keyletters are:\n"
1124 " * 'a': print the decoded command line arguments\n"
1125 " * 'e': print the exit reason\n"
1126 " * 'i': print rate processing details\n"
1127 " * 's': print first server-id\n"
1128 " * 't': when finished, print timers of all successful exchanges\n"
1129 " * 'T': when finished, print templates\n"
1130 "-X<xid-offset>: Transaction ID (aka. xid) offset in the template.\n"
1131 "\n"
1132 "DHCPv4 only options:\n"
1133 "-B: Force broadcast handling.\n"
1134 "\n"
1135 "DHCPv6 only options:\n"
1136 "-c: Add a rapid commit option (exchanges will be SA).\n"
1137 "-F<release-rate>: Rate at which IPv6 Release requests are sent to\n"
1138 " a server. This value is only valid when used in conjunction with\n"
1139 " the exchange rate (given by -r<rate>). Furthermore the sum of\n"
1140 " this value and the renew-rate (given by -f<rate) must be equal\n"
1141 " to or less than the exchange rate.\n"
1142 "-A<encapsulation-level>: Specifies that relayed traffic must be\n"
1143 " generated. The argument specifies the level of encapsulation, i.e.\n"
1144 " how many relay agents are simulated. Currently the only supported\n"
1145 " <encapsulation-level> value is 1, which means that the generated\n"
1146 " traffic is an equivalent of the traffic passing through a single\n"
1147 " relay agent.\n"
1148 "\n"
1149 "The remaining options are typically used in conjunction with -r:\n"
1150 "\n"
1151 "-D<max-drop>: Abort the test immediately if max-drop requests have\n"
1152 " been dropped. max-drop must be a positive integer. If max-drop\n"
1153 " includes the suffix '%', it specifies a maximum percentage of\n"
1154 " requests that may be dropped before abort. In this case, testing\n"
1155 " of the threshold begins after 10 requests have been expected to\n"
1156 " be received.\n"
1157 "-n<num-request>: Initiate <num-request> transactions. No report is\n"
1158 " generated until all transactions have been initiated/waited-for,\n"
1159 " after which a report is generated and the program terminates.\n"
1160 "-p<test-period>: Send requests for the given test period, which is\n"
1161 " specified in the same manner as -d. This can be used as an\n"
1162 " alternative to -n, or both options can be given, in which case the\n"
1163 " testing is completed when either limit is reached.\n"
1164 "-t<report>: Delay in seconds between two periodic reports.\n"
1165 "\n"
1166 "Errors:\n"
1167 "- tooshort: received a too short message\n"
1168 "- orphans: received a message which doesn't match an exchange\n"
1169 " (duplicate, late or not related)\n"
1170 "- locallimit: reached to local system limits when sending a message.\n"
1171 "\n"
1172 "Exit status:\n"
1173 "The exit status is:\n"
1174 "0 on complete success.\n"
1175 "1 for a general error.\n"
1176 "2 if an error is found in the command line arguments.\n"
1177 "3 if there are no general failures in operation, but one or more\n"
1178 " exchanges are not successfully completed.\n";
1179}
1180
1181void
1183 std::cout << "VERSION: " << VERSION << std::endl;
1184}
1185
1186} // namespace perfdhcp
1187} // namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown if a parameter given to a method or function is considered invalid...
A generic exception that is thrown when an unexpected error condition occurs.
@ DUID_LLT
link-layer + time, see RFC3315, section 11.2
Definition: duid.h:40
Handles network interfaces, transmission and reception.
Definition: iface_mgr.h:478
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition: iface_mgr.cc:53
IfacePtr getIface(int ifindex)
Returns interface specified interface index.
Definition: iface_mgr.cc:752
void set(const Type lease_type)
Sets the lease type code.
void fromCommandLine(const std::string &cmd_line_arg)
Sets the lease type from the command line argument.
bool is(const Type lease_type) const
Checks if lease type has the specified code.
std::string toText() const
Return textual representation of the lease type.
bool includes(const Type lease_type) const
Checks if lease type implies request for the address, prefix (or both) as specified by the function a...
int getServerIdOffset() const
Returns template offset for server-ID.
int getRenewRate() const
Returns a rate at which DHCPv6 Renew messages are sent.
uint8_t getIpVersion() const
Returns IP version.
int getRate() const
Returns exchange rate.
void version() const
Print program version.
bool isRapidCommit() const
Check if rapid commit option used.
bool isUseFirst() const
Check if server-ID to be taken from first package.
int getLocalPort() const
Returns local port number.
std::string getMacListFile() const
Returns location of the file containing list of MAC addresses.
std::vector< int > getRandomOffset() const
Returns template offsets for rnd.
std::vector< double > getMaxDropPercentage() const
Returns maximal percentage of drops.
bool isBroadcast() const
Checks if broadcast address is to be used.
std::vector< std::string > getTemplateFiles() const
Returns template file names.
std::vector< int > getTransactionIdOffset() const
brief Returns template offsets for xid.
int getElapsedTimeOffset() const
Returns template offset for elapsed time.
std::vector< int > getNumRequests() const
Returns maximum number of exchanges.
LeaseType getLeaseType() const
\ brief Returns the type of lease being requested.
bool isUseRelayedV6() const
Check if generated DHCPv6 messages should appear as relayed.
static CommandOptions & instance()
CommandOptions is a singleton class.
void reset()
Reset to defaults.
std::vector< double > getDropTime() const
Returns drop time.
std::vector< int > getMaxDrop() const
Returns maximum drops number.
void usage() const
Print usage.
bool parse(int argc, char **const argv, bool print_cmd_line=false)
Parse command line.
int getReleaseRate() const
Returns a rate at which DHCPv6 Release messages are sent.
ExchangeMode getExchangeMode() const
Returns packet exchange mode.
void printCommandLine() const
Print command line arguments.
int getRequestedIpOffset() const
Returns template offset for requested IP.
#define DHCP_IPV4_BROADCAST_ADDRESS
Definition: dhcp4.h:42
#define ALL_DHCP_SERVERS
Definition: dhcp6.h:303
#define ALL_DHCP_RELAY_AGENTS_AND_SERVERS
Definition: dhcp6.h:302
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
const char *const config_report[]
boost::shared_ptr< Option > OptionPtr
Definition: option.h:38
const char *const * perfdhcp_config_report
void decodeHex(const string &input, vector< uint8_t > &result)
Decode a text encoded in the base16 ('hex') format into the original data.
Definition: base_n.cc:466
Defines the logger used by the top-level component of kea-dhcp-ddns.