summaryrefslogtreecommitdiffstats
path: root/src/bin/perfdhcp/test_control.h
blob: bb496e8c6cbd64c4eafb01fef145707026591cec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
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
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC")
//
// 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/.

#ifndef TEST_CONTROL_H
#define TEST_CONTROL_H

#include <perfdhcp/packet_storage.h>
#include <perfdhcp/rate_control.h>
#include <perfdhcp/stats_mgr.h>
#include <perfdhcp/receiver.h>

#include <dhcp/iface_mgr.h>
#include <dhcp/dhcp6.h>
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>
#include <util/random/random_number_generator.h>

#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>
#include <unordered_map>

namespace isc {
namespace perfdhcp {

/// 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;

/// @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) { };
};


/// \brief Test Control class.
///
/// This singleton class is used to run the performance test with
/// with \ref TestControl::run function. This function can be executed
/// multiple times if desired because it resets TestControl's internal
/// state every time it is executed. Prior to running \ref TestControl::run,
/// one must make sure to parse command line options by calling
/// \ref CommandOptions::parse. Failing to do this will result in an exception.
///
/// The following major stages of the test are performed by this class:
/// - set default transaction id and MAC address generators - the generator
/// is an object of \ref TestControl::NumberGenerator type and it provides
/// 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,
/// - 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:
///   - calculate how many packets must be send to satisfy desired rate,
///   - receive incoming packets from the server,
///   - check the exit conditions - terminate the program if the exit criteria
///   are fulfilled, e.g. reached maximum number of packet drops,
///   - send the number of packets appropriate to satisfy the desired rate,
///   - optionally print intermediate reports,
/// - print statistics, e.g. achieved rate,
/// - optionally print some diagnostics.
///
/// With the '-w' command line option user may specify the external application
/// or script to be executed. This is executed twice, first when the test starts
/// 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'.
///
/// The application is executed by calling fork() to fork the current perfdhcp
/// process and then call execlp() to replace the current process image with
/// the new one.
///
/// Option factory functions are registered using
/// \ref dhcp::LibDHCP::OptionFactoryRegister. Registered factory functions
/// provide a way to create options of the same type in the same way.
///  When a new option instance is needed, the corresponding factory
/// function is called to create it. This is done by calling
/// \ref dhcp::Option::factory with DHCP message type specified as one of
///  parameters. Some of the parameters passed to factory function
/// may be ignored (e.g. option buffer).
/// 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.
class TestControl : public boost::noncopyable {
public:
    /// \brief Default constructor.
    ///
    /// \param ignore_timestamp_reorder if true then while matching
    /// response packets to request ones negative time difference is ignored
    /// otherwise exception is raised.
    TestControl(bool ignore_timestamp_reorder);

    /// Packet template buffer.
    typedef std::vector<uint8_t> TemplateBuffer;
    /// Packet template buffers list.
    typedef std::vector<TemplateBuffer> TemplateBufferCollection;

    /// @brief Delay the exit by a fixed given time to catch up to all exchanges
    ///     that were already started.
    /// @return true if need to wait, false = ok to exit now
    bool waitToExit() const;

    /// @brief Checks if all expected packets were already received
    bool haveAllPacketsBeenReceived() const;

    /// \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
    /// (e.g. sequential or based on random function).
    class NumberGenerator {
    public:

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

        /// \brief Generate number.
        ///
        /// \return Generate number.
        virtual uint32_t generate() = 0;
    };

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

    /// \brief Sequential numbers generator class.
    class SequentialGenerator : public NumberGenerator {
    public:
        /// \brief Constructor.
        ///
        /// \param range maximum number generated. If 0 is given then
        /// range defaults to maximum uint32_t value.
        SequentialGenerator(uint32_t range = 0xFFFFFFFF) :
            NumberGenerator(),
            num_(0),
            range_(range) {
            if (range_ == 0) {
                range_ = 0xFFFFFFFF;
            }
        }

        /// \brief Generate number sequentially.
        ///
        /// \return generated number.
        virtual uint32_t generate() {
            uint32_t num = num_;
            num_ = (num_ + 1) % range_;
            return (num);
        }
    private:
        uint32_t num_;   ///< Current number.
        uint32_t range_; ///< Number of unique numbers generated.
    };

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

    /// \brief Set new transaction id generator.
    ///
    /// \param generator generator object to be used.
    void setTransidGenerator(const NumberGeneratorPtr& generator) {
        transid_gen_.reset();
        transid_gen_ = generator;
    }

    /// \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;
    }

    /// \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.
    /// @todo Instead of cleaning packets periodically we could
    /// just stop adding new packets when the certain threshold
    /// has been reached.
    void cleanCachedPackets();

    bool interrupted() const { return interrupted_; }

    StatsMgr& getStatsMgr() { return stats_mgr_; };

    void start() { receiver_.start(); }
    void stop() { receiver_.stop(); }

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

    bool serverIdReceived() const { return first_packet_serverid_.size() > 0; }
    std::string getServerId() const { return vector2Hex(first_packet_serverid_); }

    /// \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 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 uint64_t packets_num,
                     const bool preload = false);

    /// \brief Send number of DHCPREQUEST (renew) messages to a server.
    ///
    /// \param msg_num A number of messages to be sent.
    ///
    /// \return A number of messages actually sent.
    uint64_t sendMultipleRequests(const uint64_t msg_num);

    /// \brief Send number of DHCPv6 Renew or Release messages to the server.
    ///
    /// \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.
    ///
    /// \return A number of messages actually sent.
    uint64_t sendMultipleMessages6(const uint32_t msg_type,
                                   const uint64_t msg_num);

    /// \brief Pull packets from receiver and process them.
    ///
    /// It runs in a loop until there are no packets in receiver.
    unsigned int consumeReceivedPackets();

    /// \brief Print intermediate statistics.
    ///
    /// Print brief statistics regarding number of sent packets,
    /// received packets and dropped packets so far.
    void printIntermediateStats();

    /// \brief Print performance statistics.
    ///
    /// Method prints performance statistics.
    /// \throws isc::InvalidOperation if Statistics Manager was
    /// not initialized.
    void printStats() const;

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

    // We would really like following methods and members to be private but
    // 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.
protected:
    /// Generate uniformly distributed integers in range of [min, max]
    isc::util::random::UniformRandomIntegerGenerator number_generator_;

    /// \brief Creates DHCPREQUEST from a DHCPACK message.
    ///
    /// \param ack An instance of the DHCPACK message to be used to
    /// create a new message.
    ///
    /// \return Pointer to the created message.
    dhcp::Pkt4Ptr createRequestFromAck(const dhcp::Pkt4Ptr& ack);

    /// \brief Creates DHCPv6 message from the Reply packet.
    ///
    /// 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 created.
    /// \param reply An instance of the Reply packet which contents should
    /// be used to create an instance of the new message.
    ///
    /// \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);

    /// \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.
    ///
    /// \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.
    /// \throw if elapsed time buffer size is neither 2 nor 0.
    /// \return instance o the option.
    static dhcp::OptionPtr
    factoryElapsedTime6(dhcp::Option::Universe u,
                        uint16_t type,
                        const dhcp::OptionBuffer& buf);

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

    /// \brief Factory function to create IA_NA option.
    ///
    /// This factory function creates DHCPv6 IA_NA option instance.
    ///
    /// \todo add support for IA Address options.
    ///
    /// \param u universe (ignored).
    /// \param type option-type (ignored).
    /// \param buf option-buffer carrying IANA suboptions.
    /// \return instance of IA_NA option.
    static dhcp::OptionPtr factoryIana6(dhcp::Option::Universe u,
                                        uint16_t type,
                                        const dhcp::OptionBuffer& buf);

    /// \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);

    /// \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
    ///
    /// \param u universe (ignored).
    /// \param type option-type (ignored).
    /// \param buf option-buffer (ignored).
    /// \return instance of ORO option.
    static dhcp::OptionPtr
    factoryOptionRequestOption6(dhcp::Option::Universe u,
                                uint16_t type,
                                const dhcp::OptionBuffer& buf);

    /// \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.
    ///
    /// \param u universe (ignored).
    /// \param type option-type (ignored).
    /// \param buf option-buffer (ignored).
    /// \return instance of RAPID_COMMIT option..
    static dhcp::OptionPtr factoryRapidCommit6(dhcp::Option::Universe u,
                                               uint16_t type,
                                               const dhcp::OptionBuffer& buf);


    /// \brief Factory function to create DHCPv4 Request List option.
    ///
    /// This factory function creates DHCPv4 PARAMETER_REQUEST_LIST option
    /// instance with the following set of requested options:
    /// - DHO_SUBNET_MASK,
    /// - DHO_BROADCAST_ADDRESS,
    /// - DHO_TIME_OFFSET,
    /// - DHO_ROUTERS,
    /// - DHO_DOMAIN_NAME,
    /// - DHO_DOMAIN_NAME_SERVERS,
    /// - DHO_HOST_NAME.
    ///
    /// \param u universe (ignored).
    /// \param type option-type (ignored).
    /// \param buf option-buffer (ignored).
    /// \return instance o the generic option.
    static dhcp::OptionPtr factoryRequestList4(dhcp::Option::Universe u,
                                               uint16_t type,
                                               const dhcp::OptionBuffer& buf);

    /// \brief Generate DHCPv4 client identifier from HW address.
    ///
    /// This method generates DHCPv4 client identifier option from a
    /// HW address.
    ///
    /// \param hwaddr HW address.
    ///
    /// \return Pointer to an instance of the generated option.
    dhcp::OptionPtr generateClientId(const dhcp::HWAddrPtr& hwaddr) const;

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

    /// \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
    /// the MAC address template (default MAC address).
    ///
    /// \param [out] randomized number of bytes randomized (initial
    /// value is ignored).
    /// \throw isc::BadValue if MAC address template (default or specified
    /// from the command line) has invalid size (expected 6 octets).
    /// \return generated MAC address.
    std::vector<uint8_t> generateMacAddress(uint8_t& randomized);

    /// \brief generate transaction id.
    ///
    /// Generate transaction id value (32-bit for DHCPv4,
    /// 24-bit for DHCPv6).
    ///
    /// \return generated transaction id.
    uint32_t generateTransid() {
        return (transid_gen_->generate());
    }

    /// \brief Return template buffer.
    ///
    /// Method returns template buffer at specified index.
    ///
    /// \param idx index of template buffer.
    /// \throw isc::OutOfRange if buffer index out of bounds.
    /// \return reference to template buffer.
    TemplateBuffer getTemplateBuffer(const size_t idx) const;

    /// \brief Reads packet templates from files.
    ///
    /// Method iterates through all specified template files, reads
    /// their content and stores it in class internal buffers. Template
    /// file names are specified from the command line with -T option.
    ///
    /// \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.
    void initPacketTemplates();

    /// \brief Print rate statistics.
    ///
    /// Method print packet exchange rate statistics.
    void printRate() const;

    /// \brief Process received DHCPv4 packet.
    ///
    /// 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
    /// REQUEST message to the server.
    ///
    /// \warning this method does not check if provided socket is
    /// valid (specifically if v4 socket for received v4 packet).
    ///
    /// \param [in] pkt4 object representing DHCPv4 packet received.
    /// \throw isc::BadValue if unknown message type received.
    /// \throw isc::Unexpected if unexpected error occurred.
    void processReceivedPacket4(const dhcp::Pkt4Ptr& pkt4);

    /// \brief Process received DHCPv6 packet.
    ///
    /// Method performs processing of the received DHCPv6 packet,
    /// updates statistics and responds to the server if required,
    /// e.g. when ADVERTISE packet arrives, this function will initiate
    /// REQUEST message to the server.
    ///
    /// \param [in] pkt6 object representing DHCPv6 packet received.
    /// \throw isc::BadValue if unknown message type received.
    /// \throw isc::Unexpected if unexpected error occurred.
    void processReceivedPacket6(const dhcp::Pkt6Ptr& pkt6);

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

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

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


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

    /// \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 performs no operation.
    /// This function does not perform sanity check if packet
    /// 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 performs no operation.
    /// This function does not perform sanity check if packet
    /// pointer is valid. Make sure it is before calling it.
    ///
    /// \param pkt packet to be stored.
    inline void saveFirstPacket(const dhcp::Pkt6Ptr& pkt);

    /// \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
    /// as described in \ref factoryRequestList4.
    /// 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.
    /// Copy of sent packet is stored in the stats_mgr_ object to
    /// update statistics.
    ///
    /// \param preload preload mode, packets not included in statistics.
    ///
    /// \throw isc::Unexpected if failed to create new packet instance.
    /// \throw isc::BadValue if MAC address has invalid length.
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
    void sendDiscover4(const bool preload = false);

    /// \brief Send DHCPv4 DISCOVER message from template.
    ///
    /// Method sends DHCPv4 DISCOVER message from template. The
    /// template data is expected to be in binary format. Provided
    /// buffer is copied and parts of it are replaced with actual
    /// data (e.g. MAC address, transaction id etc.).
    /// Copy of sent packet is stored in the stats_mgr_ object to
    /// update statistics.
    ///
    /// \param template_buf buffer holding template packet.
    /// \param preload preload mode, packets not included in statistics.
    ///
    /// \throw isc::OutOfRange if randomization offset is out of bounds.
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
    void sendDiscover4(const std::vector<uint8_t>& template_buf,
                       const bool preload = false);

    /// \brief Send DHCPv4 renew (DHCPREQUEST).
    ///
    /// \return true if the message has been sent, false otherwise.
    bool sendRequestFromAck();

    /// \brief Send DHCPv6 Renew or Release message.
    ///
    /// This method will select an existing lease from the Reply packet cache
    /// If there is no lease that can be renewed or released this method will
    /// return false.
    ///
    /// \param msg_type A type of the message to be sent (DHCPV6_RENEW or
    /// DHCPV6_RELEASE).
    ///
    /// \return true if the message has been sent, false otherwise.
    bool sendMessageFromReply(const uint16_t msg_type);

    /// \brief Send DHCPv4 REQUEST message.
    ///
    /// Method creates and sends DHCPv4 REQUEST message to the server.
    /// Copy of sent packet is stored in the stats_mgr_ object to
    /// update statistics.
    ///
    /// \param discover_pkt4 DISCOVER packet sent.
    /// \param offer_pkt4 OFFER packet object.
    ///
    /// \throw isc::Unexpected if unexpected error occurred.
    /// \throw isc::InvalidOperation if Statistics Manager has not been
    /// initialized.
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
    void sendRequest4(const dhcp::Pkt4Ptr& discover_pkt4,
                      const dhcp::Pkt4Ptr& offer_pkt4);

    /// \brief Send DHCPv4 REQUEST message from template.
    ///
    /// Method sends DHCPv4 REQUEST message from template.
    /// Copy of sent packet is stored in the stats_mgr_ object to
    /// update statistics.
    ///
    /// \param template_buf buffer holding template packet.
    /// \param discover_pkt4 DISCOVER packet sent.
    /// \param offer_pkt4 OFFER packet received.
    ///
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
    void sendRequest4(const std::vector<uint8_t>& template_buf,
                      const dhcp::Pkt4Ptr& discover_pkt4,
                      const dhcp::Pkt4Ptr& offer_pkt4);

    /// \brief Send DHCPv6 REQUEST message.
    ///
    /// Method creates and sends DHCPv6 REQUEST message to the server
    /// with the following options:
    /// - D6O_ELAPSED_TIME
    /// - D6O_CLIENTID
    /// - D6O_SERVERID
    /// Copy of sent packet is stored in the stats_mgr_ object to
    /// update statistics.
    ///
    /// \param advertise_pkt6 ADVERTISE packet object.
    /// \throw isc::Unexpected if unexpected error occurred.
    /// \throw isc::InvalidOperation if Statistics Manager has not been
    /// initialized.
    ///
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
    void sendRequest6(const dhcp::Pkt6Ptr& advertise_pkt6);

    /// \brief Send DHCPv6 REQUEST message from template.
    ///
    /// Method sends DHCPv6 REQUEST message from template.
    /// Copy of sent packet is stored in the stats_mgr_ object to
    /// update statistics.
    ///
    /// \param template_buf packet template buffer.
    /// \param advertise_pkt6 ADVERTISE packet object.
    ///
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
    void sendRequest6(const std::vector<uint8_t>& template_buf,
                      const dhcp::Pkt6Ptr& advertise_pkt6);

    /// \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.
    /// Copy of sent packet is stored in the stats_mgr_ object to
    /// update statistics.
    ///
    /// \param preload mode, packets not included in statistics.
    ///
    /// \throw isc::Unexpected if failed to create new packet instance.
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
    void sendSolicit6(const bool preload = false);

    /// \brief Send DHCPv6 SOLICIT message from template.
    ///
    /// Method sends DHCPv6 SOLICIT message from template.
    /// Copy of sent packet is stored in the stats_mgr_ object to
    /// update statistics.
    ///
    /// \param template_buf packet template buffer.
    /// \param preload mode, packets not included in statistics.
    ///
    /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
    void sendSolicit6(const std::vector<uint8_t>& template_buf,
                      const bool preload = false);

    /// \brief Set default DHCPv4 packet parameters.
    ///
    /// This method sets default parameters on the DHCPv4 packet:
    /// - 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)
    ///
    /// \param pkt reference to packet to be configured.
    void setDefaults4(const dhcp::Pkt4Ptr& pkt);

    /// \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 pkt reference to packet to be configured.
    void setDefaults6(const dhcp::Pkt6Ptr& pkt);

    /// @brief Inserts extra options specified by user.
    ///
    /// Note: addExtraOpts for v4 and v6 could easily be turned into a template.
    /// However, this would require putting code here that uses CommandOptions,
    /// and that would create dependency between test_control.h and
    /// command_options.h.
    ///
    /// @param pkt4 options will be added here
    void addExtraOpts(const dhcp::Pkt4Ptr& pkt4);

    /// @brief Inserts extra options specified by user.
    ///
    /// Note: addExtraOpts for v4 and v6 could easily be turned into a template.
    /// However, this would require putting code here that uses CommandOptions,
    /// and that would create dependency between test_control.h and
    /// command_options.h.
    ///
    /// @param pkt6 options will be added here
    void addExtraOpts(const dhcp::Pkt6Ptr& pkt6);

    /// \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.
    /// \param [out] pkt_to A packet to which options should be copied.
    ///
    /// \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);

    /// \brief Convert binary value to hex string.
    ///
    /// \todo Consider moving this function to src/lib/util.
    ///
    /// \param b byte to convert.
    /// \return hex string.
    std::string byte2Hex(const uint8_t b) const;

    /// \brief Calculate elapsed time between two packets.
    ///
    /// 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.
    ///
    /// \tparam T Pkt4Ptr or Pkt6Ptr class.
    /// \param pkt1 first packet.
    /// \param pkt2 second packet.
    /// \throw InvalidOperation if packet timestamps are invalid.
    /// \return elapsed time in milliseconds between pkt1 and pkt2.
    template<class T>
    uint32_t getElapsedTime(const T& pkt1, const T& pkt2);

    /// \brief Return elapsed time offset in a packet.
    ///
    /// \return elapsed time offset in packet.
    int getElapsedTimeOffset() const;

    /// \brief Return randomization offset in a packet.
    ///
    /// \return randomization offset in packet.
    int getRandomOffset(const int arg_idx) const;

    /// \brief Return requested ip offset in a packet.
    ///
    /// \return randomization offset in a packet.
    int getRequestedIpOffset() const;

    /// \brief Return server id offset in a packet.
    ///
    /// \return server id offset in packet.
    int getServerIdOffset() const;

    /// \brief Return transaction id offset in a packet.
    ///
    /// \param arg_idx command line argument index to be used.
    /// If multiple -X parameters specified it points to the
    /// one to be used.
    /// \return transaction id offset in packet.
    int getTransactionIdOffset(const int arg_idx) const;

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

    /// \brief Handle child signal.
    ///
    /// Function handles child signal by waiting for
    /// the process to complete.
    ///
    /// \param sig signal (ignored)
    static void handleChild(int sig);

    /// \brief Handle interrupt signal.
    ///
    /// Function sets flag indicating that program has been
    /// interrupted.
    ///
    /// \param sig signal (ignored)
    static void handleInterrupt(int sig);

    /// \brief Print main diagnostics data.
    ///
    /// Method prints main diagnostics data.
    void printDiagnostics() const;

    /// \brief Print template information
    ///
    /// \param packet_type packet type.
    void printTemplate(const uint8_t packet_type) const;

    /// \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.
    ///
    /// \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.
    void readPacketTemplate(const std::string& file_name);

    PerfSocket socket_;
    Receiver receiver_;

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

    StatsMgr stats_mgr_;  ///< Statistics Manager.

    PacketStorage<dhcp::Pkt4> ack_storage_; ///< A storage for DHCPACK messages.
    PacketStorage<dhcp::Pkt6> reply_storage_; ///< A storage for reply messages.

    NumberGeneratorPtr transid_gen_; ///< Transaction id generator.
    NumberGeneratorPtr macaddr_gen_; ///< Numbers generator for MAC address.

    /// Buffer holding server id received in first packet
    dhcp::OptionBuffer first_packet_serverid_;

    /// Packet template buffers.
    TemplateBufferCollection template_buffers_;

    /// First packets send. They are used at the end of the test
    /// to print packet templates when diagnostics flag T is specified.
    std::map<uint8_t, dhcp::Pkt4Ptr> template_packets_v4_;
    std::map<uint8_t, dhcp::Pkt6Ptr> template_packets_v6_;

    static bool interrupted_;  ///< Is program interrupted.
};

}  // namespace perfdhcp
}  // namespace isc

#endif // TEST_CONTROL_H