command_options.cc 44 KB

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