command_options.cc 44.2 KB
Newer Older
1
// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
2
//
3 4 5
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6

7 8
#include <config.h>

9 10 11 12
#include "command_options.h"
#include <exceptions/exceptions.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/duid.h>
13
#include <cfgrpt/config_report.h>
14 15 16 17 18

#include <boost/lexical_cast.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

#include <sstream>
19
#include <stdio.h>
20 21
#include <stdlib.h>
#include <stdint.h>
22
#include <unistd.h>
23
#include <fstream>
24 25 26 27 28 29 30 31


using namespace std;
using namespace isc;

namespace isc {
namespace perfdhcp {

32 33 34
// Refer to config_report so it will be embedded in the binary
const char* const* perfdhcp_config_report = isc::detail::config_report;

35
CommandOptions::LeaseType::LeaseType()
36
    : type_(ADDRESS) {
37 38 39 40 41 42 43 44 45 46 47
}

CommandOptions::LeaseType::LeaseType(const Type lease_type)
    : type_(lease_type) {
}

bool
CommandOptions::LeaseType::is(const Type lease_type) const {
    return (lease_type == type_);
}

48 49 50 51 52
bool
CommandOptions::LeaseType::includes(const Type lease_type) const {
    return (is(ADDRESS_AND_PREFIX) || (lease_type == type_));
}

53 54 55 56 57 58 59 60
void
CommandOptions::LeaseType::set(const Type lease_type) {
    type_ = lease_type;
}

void
CommandOptions::LeaseType::fromCommandLine(const std::string& cmd_line_arg) {
    if (cmd_line_arg == "address-only") {
61
        type_ = ADDRESS;
62 63

    } else if (cmd_line_arg == "prefix-only") {
64
        type_ = PREFIX;
65

66 67 68
    } else if (cmd_line_arg == "address-and-prefix") {
        type_ = ADDRESS_AND_PREFIX;

69 70 71 72 73 74 75 76 77 78
    } else {
        isc_throw(isc::InvalidParameter, "value of lease-type: -e<lease-type>,"
                  " must be one of the following: 'address-only' or"
                  " 'prefix-only'");
    }
}

std::string
CommandOptions::LeaseType::toText() const {
    switch (type_) {
79
    case ADDRESS:
80
        return ("address-only (IA_NA option added to the client's request)");
81
    case PREFIX:
82
        return ("prefix-only (IA_PD option added to the client's request)");
83 84 85
    case ADDRESS_AND_PREFIX:
        return ("address-and-prefix (Both IA_NA and IA_PD options added to the"
                " client's request)");
86 87 88 89 90 91
    default:
        isc_throw(Unexpected, "internal error: undefined lease type code when"
                  " returning textual representation of the lease type");
    }
}

92 93
CommandOptions&
CommandOptions::instance() {
94 95
    static CommandOptions options;
    return (options);
96 97
}

98 99
void
CommandOptions::reset() {
100
    // Default mac address used in DHCP messages
101
    // if -b mac=<mac-address> was not specified
102
    uint8_t mac[6] = { 0x0, 0xC, 0x1, 0x2, 0x3, 0x4 };
103

104
    // Default packet drop time if -D<drop-time> parameter
105 106
    // was not specified
    double dt[2] = { 1., 1. };
107

Marcin Siodelski's avatar
Marcin Siodelski committed
108 109
    // We don't use constructor initialization list because we
    // will need to reset all members many times to perform unit tests
110
    ipversion_ = 0;
111
    exchange_mode_ = DORA_SARR;
112
    lease_type_.set(LeaseType::ADDRESS);
113
    rate_ = 0;
114
    renew_rate_ = 0;
115
    release_rate_ = 0;
116
    report_delay_ = 0;
117
    clients_num_ = 0;
118 119
    mac_template_.assign(mac, mac + 6);
    duid_template_.clear();
120
    base_.clear();
121
    mac_list_file_.clear();
122
    mac_list_.clear();
123
    num_request_.clear();
124
    period_ = 0;
125 126 127 128 129
    drop_time_set_ = 0;
    drop_time_.assign(dt, dt + 2);
    max_drop_.clear();
    max_pdrop_.clear();
    localname_.clear();
130 131 132 133 134 135 136 137 138
    is_interface_ = false;
    preload_ = 0;
    aggressivity_ = 1;
    local_port_ = 0;
    seeded_ = false;
    seed_ = 0;
    broadcast_ = false;
    rapid_commit_ = false;
    use_first_ = false;
139 140 141
    template_file_.clear();
    rnd_offset_.clear();
    xid_offset_.clear();
142 143 144
    elp_offset_ = -1;
    sid_offset_ = -1;
    rip_offset_ = -1;
145 146 147
    diags_.clear();
    wrapped_.clear();
    server_name_.clear();
148
    v6_relay_encapsulation_level_ = 0;
149
    generateDuidTemplate();
150 151
}

152
bool
153
CommandOptions::parse(int argc, char** const argv, bool print_cmd_line) {
154
    // Reset internal variables used by getopt
Marcin Siodelski's avatar
Marcin Siodelski committed
155
    // to eliminate undefined behavior when
156
    // parsing different command lines multiple times
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172

#ifdef __GLIBC__
    // Warning: non-portable code. This is due to a bug in glibc's
    // getopt() which keeps internal state about an old argument vector
    // (argc, argv) from last call and tries to scan them when a new
    // argument vector (argc, argv) is passed. As the old vector may not
    // be main()'s arguments, but heap allocated and may have been freed
    // since, this becomes a use after free and results in random
    // behavior. According to the NOTES section in glibc getopt()'s
    // manpage, setting optind=0 resets getopt()'s state. Though this is
    // not required in our usage of getopt(), the bug still happens
    // unless we set optind=0.
    //
    // Setting optind=0 is non-portable code.
    optind = 0;
#else
173
    optind = 1;
174 175
#endif

176 177 178 179 180 181 182 183 184
    // optreset is declared on BSD systems and is used to reset internal
    // state of getopt(). When parsing command line arguments multiple
    // times with getopt() the optreset must be set to 1 every time before
    // parsing starts. Failing to do so will result in random behavior of
    // getopt().
#ifdef HAVE_OPTRESET
    optreset = 1;
#endif

185
    opterr = 0;
186 187 188 189

    // Reset values of class members
    reset();

190
    // Informs if program has been run with 'h' or 'v' option.
191
    bool help_or_version_mode = initialize(argc, argv, print_cmd_line);
192 193 194 195
    if (!help_or_version_mode) {
        validate();
    }
    return (help_or_version_mode);
196 197
}

198
bool
199
CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
200
    int opt = 0;                // Subsequent options returned by getopt()
201 202
    std::string drop_arg;       // Value of -D<value>argument
    size_t percent_loc = 0;     // Location of % sign in -D<value>
203
    double drop_percent = 0;    // % value (1..100) in -D<value%>
204
    int num_drops = 0;          // Max number of drops specified in -D<value>
205 206
    int num_req = 0;            // Max number of dropped
                                // requests in -n<max-drops>
207 208 209
    int offset_arg = 0;         // Temporary variable holding offset arguments
    std::string sarg;           // Temporary variable for string args

210 211
    std::ostringstream stream;
    stream << "perfdhcp";
212
    int num_mac_list_files = 0;
213

214
    // In this section we collect argument values from command line
Marcin Siodelski's avatar
Marcin Siodelski committed
215
    // they will be tuned and validated elsewhere
216
    while((opt = getopt(argc, argv, "hv46A:r:t:R:b:n:p:d:D:l:P:a:L:M:"
217
                        "s:iBc1T:X:O:E:S:I:x:w:e:f:F:")) != -1) {
218
        stream << " -" << static_cast<char>(opt);
219 220
        if (optarg) {
            stream << " " << optarg;
221
        }
222
        switch (opt) {
223 224 225 226
        case '1':
            use_first_ = true;
            break;

227
        // Simulate DHCPv6 relayed traffic.
228
        case 'A':
229 230
            // @todo: At the moment we only support simulating a single relay
            // agent. In the future we should extend it to up to 32.
231
            // See comment in https://github.com/isc-projects/kea/pull/22#issuecomment-243405600
232
            v6_relay_encapsulation_level_ =
233
                static_cast<uint8_t>(positiveInteger("-A<encapsulation-level> must"
234 235 236
                                                     " be a positive integer"));
            if (v6_relay_encapsulation_level_ != 1) {
                isc_throw(isc::InvalidParameter, "-A only supports 1 at the moment.");
237 238 239
            }
            break;

240 241 242 243
        case '4':
            check(ipversion_ == 6, "IP version already set to 6");
            ipversion_ = 4;
            break;
244

245 246 247 248
        case '6':
            check(ipversion_ == 4, "IP version already set to 4");
            ipversion_ = 6;
            break;
Marcin Siodelski's avatar
Marcin Siodelski committed
249

250
        case 'a':
251 252
            aggressivity_ = positiveInteger("value of aggressivity: -a<value>"
                                            " must be a positive integer");
253
            break;
254

255
        case 'b':
256
            check(base_.size() > 3, "-b<value> already specified,"
257
                  " unexpected occurrence of 5th -b<value>");
258 259 260 261
            base_.push_back(optarg);
            decodeBase(base_.back());
            break;

262 263
        case 'B':
            broadcast_ = true;
264
            break;
265

266 267
        case 'c':
            rapid_commit_ = true;
268
            break;
269

270
        case 'd':
271 272
            check(drop_time_set_ > 1,
                  "maximum number of drops already specified, "
273
                  "unexpected 3rd occurrence of -d<value>");
274
            try {
275 276
                drop_time_[drop_time_set_] =
                    boost::lexical_cast<double>(optarg);
277 278
            } catch (boost::bad_lexical_cast&) {
                isc_throw(isc::InvalidParameter,
279 280
                          "value of drop time: -d<value>"
                          " must be positive number");
281
            }
282 283
            check(drop_time_[drop_time_set_] <= 0.,
                  "drop-time must be a positive number");
284
            drop_time_set_ = true;
285
            break;
286

287
        case 'D':
288 289
            drop_arg = std::string(optarg);
            percent_loc = drop_arg.find('%');
290 291
            check(max_pdrop_.size() > 1 || max_drop_.size() > 1,
                  "values of maximum drops: -D<value> already "
292
                  "specified, unexpected 3rd occurrence of -D<value>");
293 294
            if ((percent_loc) != std::string::npos) {
                try {
295 296
                    drop_percent =
                        boost::lexical_cast<double>(drop_arg.substr(0, percent_loc));
297 298
                } catch (boost::bad_lexical_cast&) {
                    isc_throw(isc::InvalidParameter,
299 300
                              "value of drop percentage: -D<value%>"
                              " must be 0..100");
301 302
                }
                check((drop_percent <= 0) || (drop_percent >= 100),
303
                  "value of drop percentage: -D<value%> must be 0..100");
304 305
                max_pdrop_.push_back(drop_percent);
            } else {
306 307
                num_drops = positiveInteger("value of max drops number:"
                                            " -d<value> must be a positive integer");
308
                max_drop_.push_back(num_drops);
309 310
            }
            break;
311

312 313 314 315
        case 'e':
            initLeaseType();
            break;

316
        case 'E':
317 318
            elp_offset_ = nonNegativeInteger("value of time-offset: -E<value>"
                                             " must not be a negative integer");
319
            break;
320

321 322 323 324 325
        case 'f':
            renew_rate_ = positiveInteger("value of the renew rate: -f<renew-rate>"
                                          " must be a positive integer");
            break;

326 327 328 329 330 331
        case 'F':
            release_rate_ = positiveInteger("value of the release rate:"
                                            " -F<release-rate> must be a"
                                            " positive integer");
            break;

332 333
        case 'h':
            usage();
334
            return (true);
335 336 337

        case 'i':
            exchange_mode_ = DO_SA;
338
            break;
339

340
        case 'I':
341 342 343
            rip_offset_ = positiveInteger("value of ip address offset:"
                                          " -I<value> must be a"
                                          " positive integer");
344 345 346 347
            break;

        case 'l':
            localname_ = std::string(optarg);
348
            initIsInterface();
349 350 351
            break;

        case 'L':
352 353 354 355 356
             local_port_ = nonNegativeInteger("value of local port:"
                                              " -L<value> must not be a"
                                              " negative integer");
             check(local_port_ >
                   static_cast<int>(std::numeric_limits<uint16_t>::max()),
357 358 359 360
                  "local-port must be lower than " +
                  boost::lexical_cast<std::string>(std::numeric_limits<uint16_t>::max()));
            break;

361 362 363
        case 'M':
            check(num_mac_list_files >= 1, "only -M option can be specified");
            num_mac_list_files++;
364
            mac_list_file_ = std::string(optarg);
365 366 367
            loadMacs();
            break;

368
        case 'n':
369 370
            num_req = positiveInteger("value of num-request:"
                                      " -n<value> must be a positive integer");
371
            if (num_request_.size() >= 2) {
372 373
                isc_throw(isc::InvalidParameter,
                          "value of maximum number of requests: -n<value> "
374
                          "already specified, unexpected 3rd occurrence"
375
                          " of -n<value>");
376 377
            }
            num_request_.push_back(num_req);
378 379
            break;

380 381
        case 'O':
            if (rnd_offset_.size() < 2) {
382 383
                offset_arg = positiveInteger("value of random offset: "
                                             "-O<value> must be greater than 3");
384 385
            } else {
                isc_throw(isc::InvalidParameter,
386
                          "random offsets already specified,"
387
                          " unexpected 3rd occurrence of -O<value>");
388
            }
389 390
            check(offset_arg < 3, "value of random random-offset:"
                  " -O<value> must be greater than 3 ");
391
            rnd_offset_.push_back(offset_arg);
392 393
            break;

394
        case 'p':
395 396
            period_ = positiveInteger("value of test period:"
                                      " -p<value> must be a positive integer");
397 398
            break;

399
        case 'P':
400 401
            preload_ = nonNegativeInteger("number of preload packets:"
                                          " -P<value> must not be "
402
                                          "a negative integer");
403 404
            break;

405
        case 'r':
406 407
            rate_ = positiveInteger("value of rate:"
                                    " -r<value> must be a positive integer");
408 409
            break;

410 411
        case 'R':
            initClientsNum();
412 413
            break;

414 415
        case 's':
            seed_ = static_cast<unsigned int>
416 417
                (nonNegativeInteger("value of seed:"
                                    " -s <seed> must be non-negative integer"));
418
            seeded_ = seed_ > 0 ? true : false;
419 420
            break;

421
        case 'S':
422 423 424
            sid_offset_ = positiveInteger("value of server id offset:"
                                          " -S<value> must be a"
                                          " positive integer");
425 426
            break;

427
        case 't':
428 429 430
            report_delay_ = positiveInteger("value of report delay:"
                                            " -t<value> must be a"
                                            " positive integer");
431 432
            break;

433 434
        case 'T':
            if (template_file_.size() < 2) {
435 436
                sarg = nonEmptyString("template file name not specified,"
                                      " expected -T<filename>");
437 438 439
                template_file_.push_back(sarg);
            } else {
                isc_throw(isc::InvalidParameter,
440
                          "template files are already specified,"
441
                          " unexpected 3rd -T<filename> occurrence");
442
            }
443 444
            break;

445 446 447 448
        case 'v':
            version();
            return (true);

449
        case 'w':
450 451
            wrapped_ = nonEmptyString("command for wrapped mode:"
                                      " -w<command> must be specified");
452 453 454
            break;

        case 'x':
455 456
            diags_ = nonEmptyString("value of diagnostics selectors:"
                                    " -x<value> must be specified");
457 458
            break;

459 460
        case 'X':
            if (xid_offset_.size() < 2) {
461 462 463
                offset_arg = positiveInteger("value of transaction id:"
                                             " -X<value> must be a"
                                             " positive integer");
464 465
            } else {
                isc_throw(isc::InvalidParameter,
466
                          "transaction ids already specified,"
467
                          " unexpected 3rd -X<value> occurrence");
468 469
            }
            xid_offset_.push_back(offset_arg);
470 471 472
            break;

        default:
473
            isc_throw(isc::InvalidParameter, "unknown command line option");
474
        }
475 476
    }

477 478
    // If the IP version was not specified in the
    // command line, assume IPv4.
479
    if (ipversion_ == 0) {
480
        ipversion_ = 4;
481
    }
Marcin Siodelski's avatar
Marcin Siodelski committed
482

483 484 485 486 487
    // If template packet files specified for both DISCOVER/SOLICIT
    // and REQUEST/REPLY exchanges make sure we have transaction id
    // and random duid offsets for both exchanges. We will duplicate
    // value specified as -X<value> and -R<value> for second
    // exchange if user did not specified otherwise.
488
    if (template_file_.size() > 1) {
489
        if (xid_offset_.size() == 1) {
490
            xid_offset_.push_back(xid_offset_[0]);
491 492
        }
        if (rnd_offset_.size() == 1) {
493
            rnd_offset_.push_back(rnd_offset_[0]);
494
        }
495
    }
496

Marcin Siodelski's avatar
Marcin Siodelski committed
497
    // Get server argument
498 499 500
    // NoteFF02::1:2 and FF02::1:3 are defined in RFC3315 as
    // All_DHCP_Relay_Agents_and_Servers and All_DHCP_Servers
    // addresses
501 502 503
    check(optind < argc -1, "extra arguments?");
    if (optind == argc - 1) {
        server_name_ = argv[optind];
504
        stream << " " << server_name_;
Marcin Siodelski's avatar
Marcin Siodelski committed
505
        // Decode special cases
506
        if ((ipversion_ == 4) && (server_name_.compare("all") == 0)) {
507 508 509
            broadcast_ = true;
            // Use broadcast address as server name.
            server_name_ = DHCP_IPV4_BROADCAST_ADDRESS;
510
        } else if ((ipversion_ == 6) && (server_name_.compare("all") == 0)) {
511
            server_name_ = ALL_DHCP_RELAY_AGENTS_AND_SERVERS;
512 513
        } else if ((ipversion_ == 6) &&
                   (server_name_.compare("servers") == 0)) {
514
            server_name_ = ALL_DHCP_SERVERS;
515 516
        }
    }
517

518 519 520
    if (print_cmd_line) {
        std::cout << "Running: " << stream.str() << std::endl;
    }
521

522 523 524 525
    // Handle the local '-l' address/interface
    if (!localname_.empty()) {
        if (server_name_.empty()) {
            if (is_interface_ && (ipversion_ == 4)) {
526 527
                broadcast_ = true;
                server_name_ = DHCP_IPV4_BROADCAST_ADDRESS;
528
            } else if (is_interface_ && (ipversion_ == 6)) {
529
                server_name_ = ALL_DHCP_RELAY_AGENTS_AND_SERVERS;
530 531 532 533 534
            }
        }
    }
    if (server_name_.empty()) {
        isc_throw(InvalidParameter,
535
                  "without an interface, server is required");
536
    }
537 538 539

    // If DUID is not specified from command line we need to
    // generate one.
540
    if (duid_template_.empty()) {
541
        generateDuidTemplate();
542
    }
543
    return (false);
544 545
}

546
void
547
CommandOptions::initClientsNum() {
548 549
    const std::string errmsg =
        "value of -R <value> must be non-negative integer";
550

551
    try {
552 553 554 555 556
        // Declare clients_num as as 64-bit signed value to
        // be able to detect negative values provided
        // by user. We would not detect negative values
        // if we casted directly to unsigned value.
        long long clients_num = boost::lexical_cast<long long>(optarg);
557
        check(clients_num < 0, errmsg);
558
        clients_num_ = boost::lexical_cast<uint32_t>(optarg);
559 560 561 562 563
    } catch (boost::bad_lexical_cast&) {
        isc_throw(isc::InvalidParameter, errmsg);
    }
}

564 565 566 567 568 569 570 571 572 573 574
void
CommandOptions::initIsInterface() {
    is_interface_ = false;
    if (!localname_.empty()) {
        dhcp::IfaceMgr& iface_mgr = dhcp::IfaceMgr::instance();
        if (iface_mgr.getIface(localname_) != NULL)  {
            is_interface_ = true;
        }
    }
}

575 576 577 578
void
CommandOptions::decodeBase(const std::string& base) {
    std::string b(base);
    boost::algorithm::to_lower(b);
Marcin Siodelski's avatar
Marcin Siodelski committed
579

580
    // Currently we only support mac and duid
581
    if ((b.substr(0, 4) == "mac=") || (b.substr(0, 6) == "ether=")) {
582
        decodeMacBase(b);
583 584
    } else if (b.substr(0, 5) == "duid=") {
        decodeDuid(b);
585 586
    } else {
        isc_throw(isc::InvalidParameter,
587 588
                  "base value not provided as -b<value>,"
                  " expected -b mac=<mac> or -b duid=<duid>");
589 590 591 592
    }
}

void
593
CommandOptions::decodeMacBase(const std::string& base) {
Marcin Siodelski's avatar
Marcin Siodelski committed
594
    // Strip string from mac=
595
    size_t found = base.find('=');
596
    static const char* errmsg = "expected -b<base> format for"
597 598
        " mac address is -b mac=00::0C::01::02::03::04 or"
        " -b mac=00:0C:01:02:03:04";
599
    check(found == std::string::npos, errmsg);
Marcin Siodelski's avatar
Marcin Siodelski committed
600

601 602
    // Decode mac address to vector of uint8_t
    std::istringstream s1(base.substr(found + 1));
603
    std::string token;
604
    mac_template_.clear();
605
    // Get pieces of MAC address separated with : (or even ::)
606
    while (std::getline(s1, token, ':')) {
607
        // Convert token to byte value using std::istringstream
608
        if (token.length() > 0) {
609
            unsigned int ui = 0;
610
            try {
611
                // Do actual conversion
612 613 614 615 616 617
                ui = convertHexString(token);
            } catch (isc::InvalidParameter&) {
                isc_throw(isc::InvalidParameter,
                          "invalid characters in MAC provided");

            }
618
            // If conversion succeeded store byte value
619
            mac_template_.push_back(ui);
620
        }
621
    }
622
    // MAC address must consist of 6 octets, otherwise it is invalid
623
    check(mac_template_.size() != 6, errmsg);
624 625 626 627
}

void
CommandOptions::decodeDuid(const std::string& base) {
Marcin Siodelski's avatar
Marcin Siodelski committed
628
    // Strip argument from duid=
629
    std::vector<uint8_t> duid_template;
630
    size_t found = base.find('=');
631 632
    check(found == std::string::npos, "expected -b<base>"
          " format for duid is -b duid=<duid>");
633
    std::string b = base.substr(found + 1);
Marcin Siodelski's avatar
Marcin Siodelski committed
634

635
    // DUID must have even number of digits and must not be longer than 64 bytes
636 637 638 639
    check(b.length() & 1, "odd number of hexadecimal digits in duid");
    check(b.length() > 128, "duid too large");
    check(b.length() == 0, "no duid specified");

640
    // Turn pairs of hexadecimal digits into vector of octets
641
    for (size_t i = 0; i < b.length(); i += 2) {
642
        unsigned int ui = 0;
643
        try {
644
            // Do actual conversion
645 646 647
            ui = convertHexString(b.substr(i, 2));
        } catch (isc::InvalidParameter&) {
            isc_throw(isc::InvalidParameter,
648
                      "invalid characters in DUID provided,"
649
                      " expected hex digits");
650
        }
651
        duid_template.push_back(static_cast<uint8_t>(ui));
652
    }
653 654 655 656
    // @todo Get rid of this limitation when we manage add support
    // for DUIDs other than LLT. Shorter DUIDs may be useful for
    // server testing purposes.
    check(duid_template.size() < 6, "DUID must be at least 6 octets long");
657
    // Assign the new duid only if successfully generated.
658
    std::swap(duid_template, duid_template_);
659 660
}

661
void
662
CommandOptions::generateDuidTemplate() {
663
    using namespace boost::posix_time;
664
    // Duid template will be most likely generated only once but
665 666
    // it is ok if it is called more then once so we simply
    //  regenerate it and discard previous value.
667 668 669
    duid_template_.clear();
    const uint8_t duid_template_len = 14;
    duid_template_.resize(duid_template_len);
670
    // The first four octets consist of DUID LLT and hardware type.
671 672
    duid_template_[0] = static_cast<uint8_t>(static_cast<uint16_t>(isc::dhcp::DUID::DUID_LLT) >> 8);
    duid_template_[1] = static_cast<uint8_t>(static_cast<uint16_t>(isc::dhcp::DUID::DUID_LLT) & 0xff);
673 674
    duid_template_[2] = HWTYPE_ETHERNET >> 8;
    duid_template_[3] = HWTYPE_ETHERNET & 0xff;
675

676 677 678 679 680 681 682
    // As described in RFC3315: 'the time value is the time
    // that the DUID is generated represented in seconds
    // since midnight (UTC), January 1, 2000, modulo 2^32.'
    ptime now = microsec_clock::universal_time();
    ptime duid_epoch(from_iso_string("20000101T000000"));
    time_period period(duid_epoch, now);
    uint32_t duration_sec = htonl(period.length().total_seconds());
683
    memcpy(&duid_template_[4], &duration_sec, 4);
684 685 686 687

    // Set link layer address (6 octets). This value may be
    // randomized before sending a packet to simulate different
    // clients.
688
    memcpy(&duid_template_[8], &mac_template_[0], 6);
689 690
}

691
uint8_t
692
CommandOptions::convertHexString(const std::string& text) const {
693
    unsigned int ui = 0;
694
    // First, check if we are dealing with hexadecimal digits only
695
    for (size_t i = 0; i < text.length(); ++i) {
696
        if (!std::isxdigit(text[i])) {
697
            isc_throw(isc::InvalidParameter,
698 699
                      "The following digit: " << text[i] << " in "
                      << text << "is not hexadecimal");
700 701
        }
    }
702 703 704 705
    // If we are here, we have valid string to convert to octet
    std::istringstream text_stream(text);
    text_stream >> std::hex >> ui >> std::dec;
    // Check if for some reason we have overflow - this should never happen!
706
    if (ui > 0xFF) {
707 708
        isc_throw(isc::InvalidParameter, "Can't convert more than"
                  " two hex digits to byte");
709 710 711 712
    }
    return ui;
}

713 714
void CommandOptions::loadMacs() {
  std::string line;
715
  std::ifstream infile(mac_list_file_.c_str());
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
  while (std::getline(infile, line)) {
    check(decodeMacString(line), "invalid mac in input");
  }
}

bool CommandOptions::decodeMacString(const std::string& line) {
  // decode mac string into a vector of uint8_t returns true in case of error.
  std::istringstream s(line);
  std::string token;
  std::vector<uint8_t> mac;
  while(std::getline(s, token, ':')) {
    // Convert token to byte value using std::istringstream
    if (token.length() > 0) {
      unsigned int ui = 0;
      try {
        // Do actual conversion
        ui = convertHexString(token);
      } catch (isc::InvalidParameter&) {
        return (true);
      }
      // If conversion succeeded store byte value
      mac.push_back(ui);
    }
  }
  mac_list_.push_back(mac);
  return (false);
}

744 745 746 747 748 749
void
CommandOptions::validate() const {
    check((getIpVersion() != 4) && (isBroadcast() != 0),
          "-B is not compatible with IPv6 (-6)");
    check((getIpVersion() != 6) && (isRapidCommit() != 0),
          "-6 (IPv6) must be set to use -c");
750 751
    check(getIpVersion() == 4 && isUseRelayedV6(),
          "Can't use -4 with -A, it's a V6 only option.");
752 753
    check((getIpVersion() != 6) && (getReleaseRate() != 0),
          "-F<release-rate> may be used with -6 (IPv6) only");
754 755
    check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1),
          "second -n<num-request> is not compatible with -i");
756
    check((getIpVersion() == 4) && !getLeaseType().is(LeaseType::ADDRESS),
757 758
          "-6 option must be used if lease type other than '-e address-only'"
          " is specified");
759
    check(!getTemplateFiles().empty() &&
760
          !getLeaseType().is(LeaseType::ADDRESS),
761
          "template files may be only used with '-e address-only'");
762
    check((getExchangeMode() == DO_SA) && (getDropTime()[1] != 1.),
763 764 765
          "second -d<drop-time> is not compatible with -i");
    check((getExchangeMode() == DO_SA) &&
          ((getMaxDrop().size() > 1) || (getMaxDropPercentage().size() > 1)),
766
          "second -D<max-drop> is not compatible with -i");
767
    check((getExchangeMode() == DO_SA) && (isUseFirst()),
768
          "-1 is not compatible with -i");
769
    check((getExchangeMode() == DO_SA) && (getTemplateFiles().size() > 1),
770
          "second -T<template-file> is not compatible with -i");
771
    check((getExchangeMode() == DO_SA) && (getTransactionIdOffset().size() > 1),
772
          "second -X<xid-offset> is not compatible with -i");
773
    check((getExchangeMode() == DO_SA) && (getRandomOffset().size() > 1),
774
          "second -O<random-offset is not compatible with -i");
775
    check((getExchangeMode() == DO_SA) && (getElapsedTimeOffset() >= 0),
776
          "-E<time-offset> is not compatible with -i");
777
    check((getExchangeMode() == DO_SA) && (getServerIdOffset() >= 0),
778
          "-S<srvid-offset> is not compatible with -i");
779
    check((getExchangeMode() == DO_SA) && (getRequestedIpOffset() >= 0),
780 781 782
          "-I<ip-offset> is not compatible with -i");
    check((getExchangeMode() == DO_SA) && (getRenewRate() != 0),
          "-f<renew-rate> is not compatible with -i");
783 784
    check((getExchangeMode() == DO_SA) && (getReleaseRate() != 0),
          "-F<release-rate> is not compatible with -i");
785
    check((getExchangeMode() != DO_SA) && (isRapidCommit() != 0),
786
          "-i must be set to use -c");
787
    check((getRate() == 0) && (getReportDelay() != 0),
788
          "-r<rate> must be set to use -t<report>");
789
    check((getRate() == 0) && (getNumRequests().size() > 0),
790
          "-r<rate> must be set to use -n<num-request>");
791
    check((getRate() == 0) && (getPeriod() != 0),
792
          "-r<rate> must be set to use -p<test-period>");
793
    check((getRate() == 0) &&
794
          ((getMaxDrop().size() > 0) || getMaxDropPercentage().size() > 0),
795
          "-r<rate> must be set to use -D<max-drop>");
796 797
    check((getRate() != 0) && (getRenewRate() + getReleaseRate() > getRate()),
          "The sum of Renew rate (-f<renew-rate>) and Release rate"
798 799
          " (-F<release-rate>) must not be greater than the exchange"
          " rate specified as -r<rate>");
800 801 802
    check((getRate() == 0) && (getRenewRate() != 0),
          "Renew rate specified as -f<renew-rate> must not be specified"
          " when -r<rate> parameter is not specified");
803 804 805
    check((getRate() == 0) && (getReleaseRate() != 0),
          "Release rate specified as -F<release-rate> must not be specified"
          " when -r<rate> parameter is not specified");
806
    check((getTemplateFiles().size() < getTransactionIdOffset().size()),
807
          "-T<template-file> must be set to use -X<xid-offset>");
808
    check((getTemplateFiles().size() < getRandomOffset().size()),
809
          "-T<template-file> must be set to use -O<random-offset>");
810
    check((getTemplateFiles().size() < 2) && (getElapsedTimeOffset() >= 0),
811
          "second/request -T<template-file> must be set to use -E<time-offset>");
812
    check((getTemplateFiles().size() < 2) && (getServerIdOffset() >= 0),
813
          "second/request -T<template-file> must be set to "
814
          "use -S<srvid-offset>");
815 816
    check((getTemplateFiles().size() < 2) && (getRequestedIpOffset() >= 0),
          "second/request -T<template-file> must be set to "
817
          "use -I<ip-offset>");
818 819
    check((!getMacListFile().empty() && base_.size() > 0),
          "Can't use -b with -M option");
820 821 822
}

void
Marcin Siodelski's avatar
Marcin Siodelski committed
823 824 825
CommandOptions::check(bool condition, const std::string& errmsg) const {
    // The same could have been done with macro or just if statement but
    // we prefer functions to macros here
826 827
    std::ostringstream stream;
    stream << errmsg << "\n";
828 829
    if (condition) {
        isc_throw(isc::InvalidParameter, errmsg);
830 831 832
    }
}

833
int
834
CommandOptions::positiveInteger(const std::string& errmsg) const {
835 836 837