test_control.h 45.2 KB
Newer Older
1
// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
2 3 4 5 6 7 8 9 10 11 12 13 14
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

15 16
#ifndef TEST_CONTROL_H
#define TEST_CONTROL_H
17

18 19 20
#include "packet_storage.h"
#include "rate_control.h"
#include "stats_mgr.h"
21

22
#include <dhcp/iface_mgr.h>
23
#include <dhcp/dhcp6.h>
24 25 26
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>

Marcin Siodelski's avatar
Marcin Siodelski committed
27 28 29 30 31 32 33
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

#include <string>
#include <vector>
34

35 36 37
namespace isc {
namespace perfdhcp {

38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
/// Default transaction id offset in the packet template.
static const size_t DHCPV4_TRANSID_OFFSET = 4;
/// Default offset of MAC's last octet in the packet template..
static const size_t DHCPV4_RANDOMIZATION_OFFSET = 35;
/// Default elapsed time offset in the packet template.
static const size_t DHCPV4_ELAPSED_TIME_OFFSET = 8;
/// Default server id offset in the packet template.
static const size_t DHCPV4_SERVERID_OFFSET = 54;
/// Default requested ip offset in the packet template.
static const size_t DHCPV4_REQUESTED_IP_OFFSET = 240;
/// Default DHCPV6 transaction id offset in t the packet template.
static const size_t DHCPV6_TRANSID_OFFSET = 1;
/// Default DHCPV6 randomization offset (last octet of DUID)
/// in the packet template.
static const size_t DHCPV6_RANDOMIZATION_OFFSET = 21;
/// Default DHCPV6 elapsed time offset in the packet template.
static const size_t DHCPV6_ELAPSED_TIME_OFFSET = 84;
/// Default DHCPV6 server id offset in the packet template.
static const size_t DHCPV6_SERVERID_OFFSET = 22;
/// Default DHCPV6 IA_NA offset in the packet template.
static const size_t DHCPV6_IA_NA_OFFSET = 40;

60 61 62 63 64 65 66
/// @brief Exception thrown when the required option is not found in a packet.
class OptionNotFound : public Exception {
public:
    OptionNotFound(const char* file, size_t line, const char* what) :
        isc::Exception(file, line, what) { };
};

67 68
/// \brief Test Control class.
///
69
/// This singleton class is used to run the performance test with
70 71
/// with \ref TestControl::run function. This function can be executed
/// multiple times if desired because it resets TestControl's internal
72
/// state every time it is executed. Prior to running \ref TestControl::run,
73
/// one must make sure to parse command line options by calling
74 75
/// \ref CommandOptions::parse. Failing to do this will result in an exception.
///
76 77
/// The following major stages of the test are performed by this class:
/// - set default transaction id and MAC address generators - the generator
78
/// is an object of \ref TestControl::NumberGenerator type and it provides
79 80 81 82 83 84 85 86 87
/// the custom randomization algorithms,
/// - print command line arguments,
/// - register option factory functions which are used to generate DHCP options
/// being sent to a server,
/// - create the socket for communication with a server,
/// - read packet templates if user specified template files with '-T' command
/// line option,
/// - set the interrupt handler (invoked when ^C is pressed) which makes
/// perfdhcp stop gracefully and print the test results before exiting,
88 89 90 91 92 93
/// - executes an external command (if specified '-w' option), e.g. if user
/// specified -w ./foo in the command line then program will execute
/// "./foo start" at the beginning of the test and "./foo stop" when the test
/// ends,
/// - initialize the Statistics Manager,
/// - executes the main loop:
94 95
///   - calculate how many packets must be send to satisfy desired rate,
///   - receive incoming packets from the server,
96
///   - check the exit conditions - terminate the program if the exit criteria
97
///   are fulfilled, e.g. reached maximum number of packet drops,
98
///   - send the number of packets appropriate to satisfy the desired rate,
99
///   - optionally print intermediate reports,
100
/// - print statistics, e.g. achieved rate,
101 102 103
/// - optionally print some diagnostics.
///
/// With the '-w' command line option user may specify the external application
Marcin Siodelski's avatar
Marcin Siodelski committed
104
/// or script to be executed. This is executed twice, first when the test starts
105 106 107 108
/// and second time when the test ends. This external script or application must
/// accept 'start' and 'stop' arguments. The first time it is called, it is
/// called with the argument 'start' and the second time with the argument
/// 'stop'.
109
///
Marcin Siodelski's avatar
Marcin Siodelski committed
110
/// The application is executed by calling fork() to fork the current perfdhcp
111 112
/// process and then call execlp() to replace the current process image with
/// the new one.
113
///
114
/// Option factory functions are registered using
115
/// \ref dhcp::LibDHCP::OptionFactoryRegister. Registered factory functions
116
/// provide a way to create options of the same type in the same way.
117
///  When a new option instance is needed, the corresponding factory
118
/// function is called to create it. This is done by calling
119
/// \ref dhcp::Option::factory with DHCP message type specified as one of
120 121
///  parameters. Some of the parameters passed to factory function
/// may be ignored (e.g. option buffer).
122 123 124 125 126 127
/// Please note that naming convention for factory functions within this
/// class is as follows:
/// - factoryABC4 - factory function for DHCPv4 option,
/// - factoryDEF6 - factory function for DHCPv6 option,
/// - factoryGHI - factory function that can be used to create either
/// DHCPv4 or DHCPv6 option.
128 129
class TestControl : public boost::noncopyable {
public:
130

131
    /// Statistics Manager for DHCPv4.
132
    typedef StatsMgr<dhcp::Pkt4> StatsMgr4;
133
    /// Pointer to Statistics Manager for DHCPv4;
134
    typedef boost::shared_ptr<StatsMgr4> StatsMgr4Ptr;
135
    /// Statictics Manager for DHCPv6.
136
    typedef StatsMgr<dhcp::Pkt6> StatsMgr6;
137
    /// Pointer to Statistics Manager for DHCPv6.
138
    typedef boost::shared_ptr<StatsMgr6> StatsMgr6Ptr;
139
    /// Packet exchange type.
140
    typedef StatsMgr<>::ExchangeType ExchangeType;
141
    /// Packet template buffer.
142
    typedef std::vector<uint8_t> TemplateBuffer;
143 144
    /// Packet template buffers list.
    typedef std::vector<TemplateBuffer> TemplateBufferCollection;
145

146 147 148 149 150 151 152 153 154
    /// \brief Socket wrapper structure.
    ///
    /// This is the wrapper that holds descriptor of the socket
    /// used to run DHCP test. The wrapped socket is closed in
    /// the destructor. This prevents resource leaks when when
    /// function that created the socket ends (normally or
    /// when exception occurs). This structure extends parent
    /// structure with new field ifindex_ that holds interface
    /// index where socket is bound to.
155
    struct TestControlSocket : public dhcp::SocketInfo {
156 157 158 159 160
        /// Interface index.
        uint16_t ifindex_;
        /// Is socket valid. It will not be valid if the provided socket
        /// descriptor does not point to valid socket.
        bool valid_;
161

162 163 164 165
        /// \brief Constructor of socket wrapper class.
        ///
        /// This constructor uses provided socket descriptor to
        /// find the name of the interface where socket has been
166 167
        /// bound to. If provided socket descriptor is invalid then
        /// valid_ field is set to false;
168 169
        ///
        /// \param socket socket descriptor.
170
        TestControlSocket(const int socket);
171

172
        /// \brief Destructor of the socket wrapper class.
173
        ///
174
        /// Destructor closes wrapped socket.
175 176 177
        ~TestControlSocket();

    private:
178
        /// \brief Initialize socket data.
179
        ///
180 181
        /// This method initializes members of the class that Interface
        /// Manager holds: interface name, local address.
182 183 184
        ///
        /// \throw isc::BadValue if interface for specified socket
        /// descriptor does not exist.
185
        void initSocketData();
186 187
    };

188 189 190 191 192
    /// \brief Number generator class.
    ///
    /// This is default numbers generator class. The member function is
    /// used to generate uint32_t values. Other generator classes should
    /// derive from this one to implement generation algorithms
193
    /// (e.g. sequential or based on random function).
194 195
    class NumberGenerator {
    public:
196 197 198 199

        /// \brief Destructor.
        virtual ~NumberGenerator() { }

200 201 202 203 204 205 206 207 208
        /// \brief Generate number.
        ///
        /// \return Generate number.
        virtual uint32_t generate() = 0;
    };

    /// The default generator pointer.
    typedef boost::shared_ptr<NumberGenerator> NumberGeneratorPtr;

209
    /// \brief Sequential numbers generator class.
210
    class SequentialGenerator : public NumberGenerator {
211
    public:
212
        /// \brief Constructor.
213
        ///
214
        /// \param range maximum number generated. If 0 is given then
215 216
        /// range defaults to maximum uint32_t value.
        SequentialGenerator(uint32_t range = 0xFFFFFFFF) :
217 218 219 220 221 222 223 224
            NumberGenerator(),
            num_(0),
            range_(range) {
            if (range_ == 0) {
                range_ = 0xFFFFFFFF;
            }
        }

225
        /// \brief Generate number sequentially.
226 227
        ///
        /// \return generated number.
228
        virtual uint32_t generate() {
229 230 231
            uint32_t num = num_;
            num_ = (num_ + 1) % range_;
            return (num);
232
        }
233 234
    private:
        uint32_t num_;   ///< Current number.
235
        uint32_t range_; ///< Number of unique numbers generated.
236 237
    };

238
    /// \brief Length of the Ethernet HW address (MAC) in bytes.
239 240 241
    ///
    /// \todo Make this variable length as there are cases when HW
    /// address is longer than this (e.g. 20 bytes).
242 243
    static const uint8_t HW_ETHER_LEN = 6;

244 245 246 247 248 249
    /// TestControl is a singleton class. This method returns reference
    /// to its sole instance.
    ///
    /// \return the only existing instance of test control
    static TestControl& instance();

250
    /// brief\ Run performance test.
251 252
    ///
    /// Method runs whole performance test. Command line options must
253
    /// be parsed prior to running this function. Otherwise function will
254 255 256
    /// throw exception.
    ///
    /// \throw isc::InvalidOperation if command line options are not parsed.
257
    /// \throw isc::Unexpected if internal Test Controller error occured.
258 259 260
    /// \return error_code, 3 if number of received packets is not equal
    /// to number of sent packets, 0 if everything is ok.
    int run();
261

262 263 264
    /// \brief Set new transaction id generator.
    ///
    /// \param generator generator object to be used.
265
    void setTransidGenerator(const NumberGeneratorPtr& generator) {
266 267 268 269
        transid_gen_.reset();
        transid_gen_ = generator;
    }

270 271 272 273 274 275 276 277 278 279 280
    /// \brief Set new MAC address generator.
    ///
    /// Set numbers generator that will be used to generate various
    /// MAC addresses to simulate number of clients.
    ///
    /// \param generator object to be used.
    void setMacAddrGenerator(const NumberGeneratorPtr& generator) {
        macaddr_gen_.reset();
        macaddr_gen_ = generator;
    }

281
    // We would really like following methods and members to be private but
282 283 284
    // they have to be accessible for unit-testing. Another, possibly better,
    // solution is to make this class friend of test class but this is not
    // what's followed in other classes.
285
protected:
286
    /// \brief Default constructor.
287
    ///
288
    /// Default constructor is protected as the object can be created
289 290 291
    /// only via \ref instance method.
    TestControl();

292
    /// \brief Check if test exit conditions fulfilled.
293
    ///
294
    /// Method checks if the test exit conditions are fulfilled.
295 296 297 298 299 300
    /// Exit conditions are checked periodically from the
    /// main loop. Program should break the main loop when
    /// this method returns true. It is calling function
    /// responsibility to break main loop gracefully and
    /// cleanup after test execution.
    ///
301
    /// \return true if any of the exit conditions is fulfilled.
302 303
    bool checkExitConditions() const;

304 305 306 307 308 309 310 311
    /// \brief Removes cached DHCPv6 Reply packets every second.
    ///
    /// This function wipes cached Reply packets from the storage.
    /// The number of packets left in the storage after the call
    /// to this function should guarantee that the Renew packets
    /// can be sent at the given rate. Note that the Renew packets
    /// are generated for the existing leases, represented here as
    /// replies from the server.
312 313 314
    /// @todo Instead of cleaning packets periodically we could
    /// just stop adding new packets when the certain threshold
    /// has been reached.
315 316
    void cleanCachedPackets();

317
    /// \brief Creates DHCPv6 message from the Reply packet.
Marcin Siodelski's avatar
Marcin Siodelski committed
318
    ///
319 320 321 322 323
    /// This function creates DHCPv6 Renew or Release message using the
    /// data from the Reply message by copying options from the Reply
    /// message.
    ///
    /// \param msg_type A type of the message to be createad.
Marcin Siodelski's avatar
Marcin Siodelski committed
324
    /// \param reply An instance of the Reply packet which contents should
325
    /// be used to create an instance of the new message.
Marcin Siodelski's avatar
Marcin Siodelski committed
326
    ///
327 328 329 330 331 332 333
    /// \return created Release or Renew message
    /// \throw isc::BadValue if the msg_type is neither DHCPV6_RENEW nor
    /// DHCPV6_RELEASE or if the reply is NULL.
    /// \throw isc::Unexpected if mandatory options are missing in the
    /// Reply message.
    dhcp::Pkt6Ptr createMessageFromReply(const uint16_t msg_type,
                                         const dhcp::Pkt6Ptr& reply);
Marcin Siodelski's avatar
Marcin Siodelski committed
334

335 336 337 338 339 340 341
    /// \brief Factory function to create DHCPv6 ELAPSED_TIME option.
    ///
    /// This factory function creates DHCPv6 ELAPSED_TIME option instance.
    /// If empty buffer is passed the option buffer will be initialized
    /// to length 2 and values will be initialized to zeros. Otherwise
    /// function will initialize option buffer with values in passed buffer.
    ///
342 343 344 345
    /// \param u universe (ignored)
    /// \param type option-type (ignored).
    /// \param buf option-buffer containing option content (2 bytes) or
    /// empty buffer if option content has to be set to default (0) value.
346 347
    /// \throw if elapsed time buffer size is neither 2 nor 0.
    /// \return instance o the option.
348
    static dhcp::OptionPtr
349 350 351
    factoryElapsedTime6(dhcp::Option::Universe u,
                        uint16_t type,
                        const dhcp::OptionBuffer& buf);
352

353 354
    /// \brief Factory function to create generic option.
    ///
355 356 357
    /// This factory function creates option with specified universe,
    /// type and buf. It does not have any additional logic validating
    /// the buffer contents, size  etc.
358 359
    ///
    /// \param u universe (V6 or V4).
360
    /// \param type option-type (ignored).
361
    /// \param buf option-buffer.
362
    /// \return instance o the option.
363 364 365 366
    static dhcp::OptionPtr factoryGeneric(dhcp::Option::Universe u,
                                          uint16_t type,
                                          const dhcp::OptionBuffer& buf);

367 368 369
    /// \brief Factory function to create IA_NA option.
    ///
    /// This factory function creates DHCPv6 IA_NA option instance.
370 371 372
    ///
    /// \todo add support for IA Address options.
    ///
373 374 375
    /// \param u universe (ignored).
    /// \param type option-type (ignored).
    /// \param buf option-buffer carrying IANA suboptions.
376
    /// \return instance of IA_NA option.
377 378 379 380
    static dhcp::OptionPtr factoryIana6(dhcp::Option::Universe u,
                                        uint16_t type,
                                        const dhcp::OptionBuffer& buf);

381 382 383 384 385 386 387 388 389 390 391
    /// \brief Factory function to create IA_PD option.
    ///
    /// this factory function creates DHCPv6 IA_PD option instance.
    ///
    /// \param u universe (ignored).
    /// \param type option-type (ignored).
    /// \param buf option-buffer carrying sub-options.
    static dhcp::OptionPtr factoryIapd6(dhcp::Option::Universe u,
                                        uint16_t type,
                                        const dhcp::OptionBuffer& buf);

392 393 394 395 396 397 398
    /// \brief Factory function to create DHCPv6 ORO option.
    ///
    /// This factory function creates DHCPv6 Option Request Option instance.
    /// The created option will contain the following set of requested options:
    /// - D6O_NAME_SERVERS
    /// - D6O_DOMAIN_SEARCH
    ///
399 400 401
    /// \param u universe (ignored).
    /// \param type option-type (ignored).
    /// \param buf option-buffer (ignored).
402
    /// \return instance of ORO option.
403 404 405 406 407
    static dhcp::OptionPtr
    factoryOptionRequestOption6(dhcp::Option::Universe u,
                                uint16_t type,
                                const dhcp::OptionBuffer& buf);

408 409 410 411 412 413
    /// \brief Factory function to create DHCPv6 RAPID_COMMIT option instance.
    ///
    /// This factory function creates DHCPv6 RAPID_COMMIT option instance.
    /// The buffer passed to this option must be empty because option does
    /// not have any payload.
    ///
414 415 416
    /// \param u universe (ignored).
    /// \param type option-type (ignored).
    /// \param buf option-buffer (ignored).
417
    /// \return instance of RAPID_COMMIT option..
418 419 420 421
    static dhcp::OptionPtr factoryRapidCommit6(dhcp::Option::Universe u,
                                               uint16_t type,
                                               const dhcp::OptionBuffer& buf);

422

423 424
    /// \brief Factory function to create DHCPv4 Request List option.
    ///
425
    /// This factory function creates DHCPv4 PARAMETER_REQUEST_LIST option
426
    /// instance with the following set of requested options:
427 428 429 430 431 432 433 434
    /// - DHO_SUBNET_MASK,
    /// - DHO_BROADCAST_ADDRESS,
    /// - DHO_TIME_OFFSET,
    /// - DHO_ROUTERS,
    /// - DHO_DOMAIN_NAME,
    /// - DHO_DOMAIN_NAME_SERVERS,
    /// - DHO_HOST_NAME.
    ///
435 436 437
    /// \param u universe (ignored).
    /// \param type option-type (ignored).
    /// \param buf option-buffer (ignored).
438 439
    /// \return instance o the generic option.
     static dhcp::OptionPtr factoryRequestList4(dhcp::Option::Universe u,
440 441 442
                                               uint16_t type,
                                               const dhcp::OptionBuffer& buf);

443 444 445
    /// \brief Generate DUID.
    ///
    /// Method generates unique DUID. The number of DUIDs it can generate
446
    /// depends on the number of simulated clients, which is specified
447
    /// from the command line. It uses \ref CommandOptions object to retrieve
448
    /// number of clients. Since the last six octets of DUID are constructed
449 450 451
    /// from the MAC address, this function uses \ref generateMacAddress
    /// internally to randomize the DUID.
    ///
452 453
    /// \todo add support for other types of DUID.
    ///
454 455
    /// \param [out] randomized number of bytes randomized (initial value
    /// is ignored).
456 457
    /// \throw isc::BadValue if \ref generateMacAddress throws.
    /// \return vector representing DUID.
458
    std::vector<uint8_t> generateDuid(uint8_t& randomized) const;
459

460 461 462 463 464 465 466
    /// \brief Generate MAC address.
    ///
    /// This method generates MAC address. The number of unique
    /// MAC addresses it can generate is determined by the number
    /// simulated DHCP clients specified from command line. It uses
    /// \ref CommandOptions object to retrieve number of clients.
    /// Based on this the random value is generated and added to
467
    /// the MAC address template (default MAC address).
468
    ///
469 470
    /// \param [out] randomized number of bytes randomized (initial
    /// value is ignored).
471
    /// \throw isc::BadValue if MAC address template (default or specified
472
    /// from the command line) has invalid size (expected 6 octets).
473
    /// \return generated MAC address.
474
    std::vector<uint8_t> generateMacAddress(uint8_t& randomized) const;
475

476 477
    /// \brief generate transaction id.
    ///
478 479 480
    /// Generate transaction id value (32-bit for DHCPv4,
    /// 24-bit for DHCPv6).
    ///
481 482
    /// \return generated transaction id.
    uint32_t generateTransid() {
483
        return (transid_gen_->generate());
484 485
    }

486 487 488 489 490 491 492 493 494
    /// \brief Returns a timeout for packet reception.
    ///
    /// The calculation is based on the value of the timestamp
    /// when the next set of packets is to be sent. If no packet is
    /// received until then, new packets are sent.
    ///
    /// \return A current timeout in microseconds.
    uint32_t getCurrentTimeout() const;

495 496 497 498 499
    /// \brief Return template buffer.
    ///
    /// Method returns template buffer at specified index.
    ///
    /// \param idx index of template buffer.
500 501
    /// \throw isc::OutOfRange if buffer index out of bounds.
    /// \return reference to template buffer.
502 503 504 505 506
    TemplateBuffer getTemplateBuffer(const size_t idx) const;

    /// \brief Reads packet templates from files.
    ///
    /// Method iterates through all specified template files, reads
507 508
    /// their content and stores it in class internal buffers. Template
    /// file names are specified from the command line with -T option.
509
    ///
510 511 512 513
    /// \throw isc::BadValue if any of the template files does not exist,
    /// contains characters other than hexadecimal digits or spaces.
    /// \throw OutOfRange if any of the template files is empty or has
    /// odd number of hexadecimal digits.
514 515
    void initPacketTemplates();

516 517 518 519 520 521
    /// \brief Initializes Statistics Manager.
    ///
    /// This function initializes Statistics Manager. If there is
    /// the one initialized already it is released.
    void initializeStatsMgr();

522 523 524
    /// \brief Open socket to communicate with DHCP server.
    ///
    /// Method opens socket and binds it to local address. Function will
525
    /// use either interface name, local address or server address
526 527 528
    /// to create a socket, depending on what is available (specified
    /// from the command line). If socket can't be created for any
    /// reason, exception is thrown.
529 530
    /// If destination address is broadcast (for DHCPv4) or multicast
    /// (for DHCPv6) than broadcast or multicast option is set on
531
    /// the socket. Opened socket is registered and managed by IfaceMgr.
532
    ///
533 534 535
    /// \throw isc::BadValue if socket can't be created for given
    /// interface, local address or remote address.
    /// \throw isc::InvalidOperation if broadcast option can't be
536
    /// set for the v4 socket or if multicast option can't be set
537 538
    /// for the v6 socket.
    /// \throw isc::Unexpected if interal unexpected error occured.
539
    /// \return socket descriptor.
540
    int openSocket() const;
541

542 543 544 545 546 547
    /// \brief Print intermediate statistics.
    ///
    /// Print brief statistics regarding number of sent packets,
    /// received packets and dropped packets so far.
    void printIntermediateStats();

548 549 550 551 552
    /// \brief Print rate statistics.
    ///
    /// Method print packet exchange rate statistics.
    void printRate() const;

553 554 555 556 557 558 559
    /// \brief Print performance statistics.
    ///
    /// Method prints performance statistics.
    /// \throws isc::InvalidOperation if Statistics Manager was
    /// not initialized.
    void printStats() const;

560
    /// \brief Process received DHCPv4 packet.
561
    ///
562 563 564
    /// Method performs processing of the received DHCPv4 packet,
    /// updates statistics and responds to the server if required,
    /// e.g. when OFFER packet arrives, this function will initiate
565 566
    /// REQUEST message to the server.
    ///
567 568 569 570 571
    /// \warning this method does not check if provided socket is
    /// valid (specifically if v4 socket for received v4 packet).
    ///
    /// \param [in] socket socket to be used.
    /// \param [in] pkt4 object representing DHCPv4 packet received.
572 573
    /// \throw isc::BadValue if unknown message type received.
    /// \throw isc::Unexpected if unexpected error occured.
574 575
    void processReceivedPacket4(const TestControlSocket& socket,
                                const dhcp::Pkt4Ptr& pkt4);
576

577
    /// \brief Process received DHCPv6 packet.
578
    ///
579
    /// Method performs processing of the received DHCPv6 packet,
580
    /// updates statistics and responds to the server if required,
581
    /// e.g. when ADVERTISE packet arrives, this function will initiate
582 583
    /// REQUEST message to the server.
    ///
584 585 586 587 588
    /// \warning this method does not check if provided socket is
    /// valid (specifically if v4 socket for received v4 packet).
    ///
    /// \param [in] socket socket to be used.
    /// \param [in] pkt6 object representing DHCPv6 packet received.
589 590
    /// \throw isc::BadValue if unknown message type received.
    /// \throw isc::Unexpected if unexpected error occured.
591 592
    void processReceivedPacket6(const TestControlSocket& socket,
                                const dhcp::Pkt6Ptr& pkt6);
593

594 595 596
    /// \brief Receive DHCPv4 or DHCPv6 packets from the server.
    ///
    /// Method receives DHCPv4 or DHCPv6 packets from the server.
597 598
    /// This function will call \ref processReceivedPacket4 or
    /// \ref processReceivedPacket6 depending if DHCPv4 or DHCPv6 packet
599 600
    /// has arrived.
    ///
601 602 603
    /// \warning this method does not check if provided socket is
    /// valid. Ensure that it is valid prior to calling it.
    ///
604
    /// \param socket socket to be used.
605 606
    /// \throw isc::BadValue if unknown message type received.
    /// \throw isc::Unexpected if unexpected error occured.
607 608
    /// \return number of received packets.
    uint64_t receivePackets(const TestControlSocket& socket);
609

610 611 612 613
    /// \brief Register option factory functions for DHCPv4
    ///
    /// Method registers option factory functions for DHCPv4.
    /// These functions are called to create instances of DHCPv4
614
    /// options. Call \ref dhcp::Option::factory to invoke factory
615 616
    /// function for particular option. Don't use this function directly.
    /// Use \ref registerOptionFactories instead.
617 618
    void registerOptionFactories4() const;

619 620 621 622
    /// \brief Register option factory functions for DHCPv6
    ///
    /// Method registers option factory functions for DHCPv6.
    /// These functions are called to create instances of DHCPv6
623
    /// options. Call \ref dhcp::Option::factory to invoke factory
624 625
    /// function for particular option. Don't use this function directly.
    /// Use \ref registerOptionFactories instead.
626 627
    void registerOptionFactories6() const;

628 629 630
    /// \brief Register option factory functions for DHCPv4 or DHCPv6.
    ///
    /// Method registers option factory functions for DHCPv4 or DHCPv6,
631
    /// depending in which mode test is currently running.
632 633
    void registerOptionFactories() const;

634

635
    /// \brief Resets internal state of the object.
636
    ///
637
    /// Method resets internal state of the object. It has to be
638 639 640
    /// called before new test is started.
    void reset();

641 642 643 644 645 646 647 648 649
    /// \brief Save the first DHCPv4 sent packet of the specified type.
    ///
    /// This method saves first packet of the specified being sent
    /// to the server if user requested diagnostics flag 'T'. In
    /// such case program has to print contents of selected packets
    /// being sent to the server. It collects first packets of each
    /// type and keeps them around until test finishes. Then they
    /// are printed to the user. If packet of specified type has
    /// been already stored this function perfroms no operation.
650
    /// This function does not perform sanity check if packet
651 652 653 654 655 656 657 658 659 660 661 662 663 664
    /// pointer is valid. Make sure it is before calling it.
    ///
    /// \param pkt packet to be stored.
    inline void saveFirstPacket(const dhcp::Pkt4Ptr& pkt);

    /// \brief Save the first DHCPv6 sent packet of the specified type.
    ///
    /// This method saves first packet of the specified being sent
    /// to the server if user requested diagnostics flag 'T'. In
    /// such case program has to print contents of selected packets
    /// being sent to the server. It collects first packets of each
    /// type and keeps them around until test finishes. Then they
    /// are printed to the user. If packet of specified type has
    /// been already stored this function perfroms no operation.
665
    /// This function does not perform sanity check if packet
666 667 668 669 670
    /// pointer is valid. Make sure it is before calling it.
    ///
    /// \param pkt packet to be stored.
    inline void saveFirstPacket(const dhcp::Pkt6Ptr& pkt);

671 672 673 674 675 676
    /// \brief Send DHCPv4 DISCOVER message.
    ///
    /// Method creates and sends DHCPv4 DISCOVER message to the server
    /// with the following options:
    /// - MESSAGE_TYPE set to DHCPDISCOVER
    /// - PARAMETER_REQUEST_LIST with the same list of requested options
677
    /// as described in \ref factoryRequestList4.
678 679 680
    /// The transaction id and MAC address are randomly generated for
    /// the message. Range of unique MAC addresses generated depends
    /// on the number of clients specified from the command line.
681 682
    /// Copy of sent packet is stored in the stats_mgr4_ object to
    /// update statistics.
683 684
    ///
    /// \param socket socket to be used to send the message.
685
    /// \param preload preload mode, packets not included in statistics.
686
    ///
687 688
    /// \throw isc::Unexpected if failed to create new packet instance.
    /// \throw isc::BadValue if MAC address has invalid length.
689
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
690 691
    void sendDiscover4(const TestControlSocket& socket,
                       const bool preload = false);
692

693 694 695
    /// \brief Send DHCPv4 DISCOVER message from template.
    ///
    /// Method sends DHCPv4 DISCOVER message from template. The
696
    /// template data is expected to be in binary format. Provided
697 698
    /// buffer is copied and parts of it are replaced with actual
    /// data (e.g. MAC address, transaction id etc.).
699 700
    /// Copy of sent packet is stored in the stats_mgr4_ object to
    /// update statistics.
701 702 703 704
    ///
    /// \param socket socket to be used to send the message.
    /// \param template_buf buffer holding template packet.
    /// \param preload preload mode, packets not included in statistics.
705
    ///
706
    /// \throw isc::OutOfRange if randomization offset is out of bounds.
707
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
708 709 710 711
    void sendDiscover4(const TestControlSocket& socket,
                       const std::vector<uint8_t>& template_buf,
                       const bool preload = false);

712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
    /// \brief Send number of packets to initiate new exchanges.
    ///
    /// Method initiates the new DHCP exchanges by sending number
    /// of DISCOVER (DHCPv4) or SOLICIT (DHCPv6) packets. If preload
    /// mode was requested sent packets will not be counted in
    /// the statistics. The responses from the server will be
    /// received and counted as orphans because corresponding sent
    /// packets are not included in StatsMgr for match.
    /// When preload mode is disabled and diagnostics flag 'i' is
    /// specified then function will be trying to receive late packets
    /// before new packets are sent to the server. Statistics of
    /// late received packets is updated accordingly.
    ///
    /// \todo do not count responses in preload mode as orphans.
    ///
    /// \param socket socket to be used to send packets.
    /// \param packets_num number of packets to be sent.
    /// \param preload preload mode, packets not included in statistics.
    /// \throw isc::Unexpected if thrown by packet sending method.
    /// \throw isc::InvalidOperation if thrown by packet sending method.
    /// \throw isc::OutOfRange if thrown by packet sending method.
    void sendPackets(const TestControlSocket &socket,
                     const uint64_t packets_num,
                     const bool preload = false);

737
    /// \brief Send number of DHCPv6 Renew or Release messages to the server.
Marcin Siodelski's avatar
Marcin Siodelski committed
738 739
    ///
    /// \param socket An object representing socket to be used to send packets.
740 741 742
    /// \param msg_type A type of the messages to be sent (DHCPV6_RENEW or
    /// DHCPV6_RELEASE).
    /// \param msg_num A number of messages to be sent.
Marcin Siodelski's avatar
Marcin Siodelski committed
743
    ///
744 745 746 747
    /// \return A number of messages actually sent.
    uint64_t sendMultipleMessages6(const TestControlSocket& socket,
                                   const uint32_t msg_type,
                                   const uint64_t msg_num);
Marcin Siodelski's avatar
Marcin Siodelski committed
748

749
    /// \brief Send DHCPv6 Renew or Release message using specified socket.
Marcin Siodelski's avatar
Marcin Siodelski committed
750
    ///
751
    /// This method will select an existing lease from the Reply packet cache
752 753
    /// If there is no lease that can be renewed or released this method will
    /// return false.
Marcin Siodelski's avatar
Marcin Siodelski committed
754
    ///
755 756
    /// \param msg_type A type of the message to be sent (DHCPV6_RENEW or
    /// DHCPV6_RELEASE).
Marcin Siodelski's avatar
Marcin Siodelski committed
757 758 759
    /// \param socket An object encapsulating socket to be used to send
    /// a packet.
    ///
760 761 762
    /// \return true if the message has been sent, false otherwise.
    bool sendMessageFromReply(const uint16_t msg_type,
                              const TestControlSocket& socket);
Marcin Siodelski's avatar
Marcin Siodelski committed
763

764 765 766
    /// \brief Send DHCPv4 REQUEST message.
    ///
    /// Method creates and sends DHCPv4 REQUEST message to the server.
767 768
    /// Copy of sent packet is stored in the stats_mgr4_ object to
    /// update statistics.
769 770
    ///
    /// \param socket socket to be used to send message.
771
    /// \param discover_pkt4 DISCOVER packet sent.
772
    /// \param offer_pkt4 OFFER packet object.
773
    ///
774 775 776
    /// \throw isc::Unexpected if unexpected error occured.
    /// \throw isc::InvalidOperation if Statistics Manager has not been
    /// initialized.
777
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
778
    void sendRequest4(const TestControlSocket& socket,
779 780 781 782 783 784
                      const dhcp::Pkt4Ptr& discover_pkt4,
                      const dhcp::Pkt4Ptr& offer_pkt4);

    /// \brief Send DHCPv4 REQUEST message from template.
    ///
    /// Method sends DHCPv4 REQUEST message from template.
785 786
    /// Copy of sent packet is stored in the stats_mgr4_ object to
    /// update statistics.
787 788 789 790 791
    ///
    /// \param socket socket to be used to send message.
    /// \param template_buf buffer holding template packet.
    /// \param discover_pkt4 DISCOVER packet sent.
    /// \param offer_pkt4 OFFER packet received.
792 793
    ///
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
794 795 796
    void sendRequest4(const TestControlSocket& socket,
                      const std::vector<uint8_t>& template_buf,
                      const dhcp::Pkt4Ptr& discover_pkt4,
797 798 799
                      const dhcp::Pkt4Ptr& offer_pkt4);

    /// \brief Send DHCPv6 REQUEST message.
800 801 802 803 804 805
    ///
    /// Method creates and sends DHCPv6 REQUEST message to the server
    /// with the following options:
    /// - D6O_ELAPSED_TIME
    /// - D6O_CLIENTID
    /// - D6O_SERVERID
806 807
    /// Copy of sent packet is stored in the stats_mgr6_ object to
    /// update statistics.
808 809 810 811 812 813
    ///
    /// \param socket socket to be used to send message.
    /// \param advertise_pkt6 ADVERTISE packet object.
    /// \throw isc::Unexpected if unexpected error occured.
    /// \throw isc::InvalidOperation if Statistics Manager has not been
    /// initialized.
814 815
    ///
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
816 817 818
    void sendRequest6(const TestControlSocket& socket,
                      const dhcp::Pkt6Ptr& advertise_pkt6);

819 820 821
    /// \brief Send DHCPv6 REQUEST message from template.
    ///
    /// Method sends DHCPv6 REQUEST message from template.
822 823
    /// Copy of sent packet is stored in the stats_mgr6_ object to
    /// update statistics.
824 825 826 827
    ///
    /// \param socket socket to be used to send message.
    /// \param template_buf packet template buffer.
    /// \param advertise_pkt6 ADVERTISE packet object.
828 829
    ///
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
830 831 832 833
    void sendRequest6(const TestControlSocket& socket,
                      const std::vector<uint8_t>& template_buf,
                      const dhcp::Pkt6Ptr& advertise_pkt6);

834 835 836 837 838 839 840 841 842
    /// \brief Send DHCPv6 SOLICIT message.
    ///
    /// Method creates and sends DHCPv6 SOLICIT message to the server
    /// with the following options:
    /// - D6O_ELAPSED_TIME,
    /// - D6O_RAPID_COMMIT if rapid commit is requested in command line,
    /// - D6O_CLIENTID,
    /// - D6O_ORO (Option Request Option),
    /// - D6O_IA_NA.
843 844
    /// Copy of sent packet is stored in the stats_mgr6_ object to
    /// update statistics.
845 846
    ///
    /// \param socket socket to be used to send the message.
847
    /// \param preload mode, packets not included in statistics.
848
    ///
849
    /// \throw isc::Unexpected if failed to create new packet instance.
850
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
851 852
    void sendSolicit6(const TestControlSocket& socket,
                      const bool preload = false);
853

854 855 856
    /// \brief Send DHCPv6 SOLICIT message from template.
    ///
    /// Method sends DHCPv6 SOLICIT message from template.
857 858
    /// Copy of sent packet is stored in the stats_mgr6_ object to
    /// update statistics.
859 860 861 862
    ///
    /// \param socket socket to be used to send the message.
    /// \param template_buf packet template buffer.
    /// \param preload mode, packets not included in statistics.
863 864
    ///
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
865 866 867 868
    void sendSolicit6(const TestControlSocket& socket,
                      const std::vector<uint8_t>& template_buf,
                      const bool preload = false);

869
    /// \brief Set default DHCPv4 packet parameters.
870
    ///
871
    /// This method sets default parameters on the DHCPv4 packet:
872 873 874 875 876 877
    /// - interface name,
    /// - local port = 68 (DHCP client port),
    /// - remote port = 67 (DHCP server port),
    /// - server's address,
    /// - GIADDR = local address where socket is bound to,
    /// - hops = 1 (pretending that we are a relay)
878
    ///
879
    /// \param socket socket used to send the packet.
880
    /// \param pkt reference to packet to be configured.
881
    void setDefaults4(const TestControlSocket& socket,
882
                      const dhcp::Pkt4Ptr& pkt);
883

884 885 886 887 888 889 890 891 892 893 894 895
    /// \brief Set default DHCPv6 packet parameters.
    ///
    /// This method sets default parameters on the DHCPv6 packet:
    /// - interface name,
    /// - interface index,
    /// - local port,
    /// - remote port,
    /// - local address,
    /// - remote address (server).
    ///
    /// \param socket socket used to send the packet.
    /// \param pkt reference to packet to be configured.
896
    void setDefaults6(const TestControlSocket& socket,
897
                      const dhcp::Pkt6Ptr& pkt);
898

899
    /// \brief Find if diagnostic flag has been set.
900 901 902 903 904
    ///
    /// \param diag diagnostic flag (a,e,i,s,r,t,T).
    /// \return true if diagnostics flag has been set.
    bool testDiags(const char diag) const;

905 906 907
protected:

    /// \brief Increments counter of late sent messages if required.
908
    ///
909 910 911 912
    /// This function checks if the message or set of messages of a given type,
    /// were sent later than their due time. If they were sent late, it is
    /// an indication that the perfdhcp doesn't catch up with the desired rate
    /// for sending messages.
913
    ///
914 915 916
    /// \param rate_control An object tracking due times for a particular
    /// type of messages.
    void checkLateMessages(RateControl& rate_control);
917

918 919 920 921 922 923 924 925 926 927
    /// \brief Copies IA_NA or IA_PD option from one packet to another.
    ///
    /// This function checks the lease-type specified in the command line
    /// with option -e<lease-type>. If 'address-only' value has been specified
    /// this function expects that IA_NA option is present in the packet
    /// encapsulated by pkt_from object. If 'prefix-only' value has been
    /// specified, this function expects that IA_PD option is present in the
    /// packet encapsulated by pkt_to object.
    ///
    /// \param [in] pkt_from A packet from which options should be copied.
928
    /// \param [out] pkt_to A packet to which options should be copied.
929 930 931 932 933 934 935
    ///
    /// \throw isc::perfdhcp::OptionNotFound if a required option is not
    /// found in the packet from which options should be copied.
    /// \throw isc::BadValue if any of the specified pointers to packets
    /// is NULL.
    void copyIaOptions(const dhcp::Pkt6Ptr& pkt_from, dhcp::Pkt6Ptr& pkt_to);

936 937
    /// \brief Convert binary value to hex string.
    ///
938 939
    /// \todo Consider moving this function to src/lib/util.
    ///
940 941 942 943
    /// \param b byte to convert.
    /// \return hex string.
    std::string byte2Hex(const uint8_t b) const;

944 945
    /// \brief Calculate elapsed time between two packets.
    ///
946 947 948 949 950
    /// This function calculates the time elapsed between two packets. If
    /// the timestamp of the pkt2 is greater than timestamp of the pkt1,
    /// the positive value is returned. If the pkt2 timestamp is equal or
    /// less than pkt1 timestamp, 0 is returned.
    ///
951
    /// \tparam T Pkt4Ptr or Pkt6Ptr class.
952 953
    /// \param pkt1 first packet.
    /// \param pkt2 second packet.
954
    /// \throw InvalidOperation if packet timestamps are invalid.
955 956
    /// \return elapsed time in milliseconds between pkt1 and pkt2.
    template<class T>
957
    uint32_t getElapsedTime(const T& pkt1, const T& pkt2);
958

959
    /// \brief Return elapsed time offset in a packet.
960 961 962 963
    ///
    /// \return elapsed time offset in packet.
    int getElapsedTimeOffset() const;

964
    /// \brief Return randomization offset in a packet.
965 966 967 968
    ///
    /// \return randomization offset in packet.
    int getRandomOffset(const int arg_idx) const;

969
    /// \brief Return requested ip offset in a packet.
970
    ///
971
    /// \return randomization offset in a packet.
972 973
    int getRequestedIpOffset() const;

974
    /// \brief Return server id offset in a packet.
975 976 977 978
    ///
    /// \return server id offset in packet.
    int getServerIdOffset() const;

979
    /// \brief Return transaction id offset in a packet.
980 981 982 983 984 985 986
    ///
    /// \param arg_idx command line argument index to be used.
    /// If multiple -X parameters specifed it points to the
    /// one to be used.
    /// \return transaction id offset in packet.
    int getTransactionIdOffset(const int arg_idx) const;

987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004
    /// \brief Get number of received packets.
    ///
    /// Get the number of received packets from the Statistics Manager.
    /// Function may throw if Statistics Manager object is not
    /// initialized.
    /// \param xchg_type packet exchange type.
    /// \return number of received packets.
    uint64_t getRcvdPacketsNum(const ExchangeType xchg_type) const;

    /// \brief Get number of sent packets.
    ///
    /// Get the number of sent packets from the Statistics Manager.
    /// Function may throw if Statistics Manager object is not
    /// initialized.
    /// \param xchg_type packet exchange type.
    /// \return number of sent packets.
    uint64_t getSentPacketsNum(const ExchangeType xchg_type) const;

1005 1006 1007 1008 1009 1010 1011 1012
    /// \brief Handle child signal.
    ///
    /// Function handles child signal by waiting for
    /// the process to complete.
    ///
    /// \param sig signal (ignored)
    static void handleChild(int sig);

1013 1014 1015
    /// \brief Handle interrupt signal.
    ///
    /// Function sets flag indicating that program has been
1016
    /// interrupted.
1017 1018 1019 1020
    ///
    /// \param sig signal (ignored)
    static void handleInterrupt(int sig);

1021 1022 1023 1024 1025
    /// \brief Print main diagnostics data.
    ///
    /// Method prints main diagnostics data.
    void printDiagnostics() const;

1026 1027 1028 1029 1030 1031 1032 1033
    /// \brief Print template information
    ///
    /// \param packet_type packet type.
    void printTemplate(const uint8_t packet_type) const;

    /// \brief Print templates information.
    ///
    /// Method prints information about data offsets
1034
    /// in packet templates and their contents.
1035 1036
    void printTemplates() const;

1037 1038 1039 1040 1041
    /// \brief Read DHCP message template from file.
    ///
    /// Method reads DHCP message template from file and
    /// converts it to binary format. Read data is appended
    /// to template_buffers_ vector.
1042 1043 1044 1045 1046 1047
    ///
    /// \param file_name name of the packet template file.
    /// \throw isc::OutOfRange if file is empty or has odd number
    /// of hexadecimal digits.
    /// \throw isc::BadValue if file contains characters other than
    /// spaces or hexadecimal digits.
1048 1049
    void readPacketTemplate(const std::string& file_name);

1050 1051 1052 1053 1054
    /// \brief Run wrapped command.
    ///
    /// \param do_stop execute wrapped command with "stop" argument.
    void runWrapped(bool do_stop = false) const;

1055 1056
    /// \brief Convert vector in hexadecimal string.
    ///
1057 1058
    /// \todo Consider moving this function to src/lib/util.
    ///
1059 1060 1061 1062 1063
    /// \param vec vector to be converted.
    /// \param separator separator.
    std::string vector2Hex(const std::vector<uint8_t>& vec,
                           const std::string& separator = "") const;

1064 1065 1066 1067 1068 1069
    /// \brief A rate control class for Discover and Solicit messages.
    RateControl basic_rate_control_;
    /// \brief A rate control class for Renew messages.
    RateControl renew_rate_control_;
    /// \brief A rate control class for Release messages.
    RateControl release_rate_control_;
1070

1071 1072
    boost::posix_time::ptime last_report_; ///< Last intermediate report time.

1073 1074
    StatsMgr4Ptr stats_mgr4_;  ///< Statistics Manager 4.
    StatsMgr6Ptr stats_mgr6_;  ///< Statistics Manager 6.