1 // Copyright (C) 2012-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #ifndef TEST_CONTROL_H
8 #define TEST_CONTROL_H
9 
10 #include <perfdhcp/packet_storage.h>
11 #include <perfdhcp/rate_control.h>
12 #include <perfdhcp/stats_mgr.h>
13 #include <perfdhcp/receiver.h>
14 #include <perfdhcp/command_options.h>
15 #include <perfdhcp/perf_socket.h>
16 #include <perfdhcp/random_number_generator.h>
17 
18 #include <dhcp/iface_mgr.h>
19 #include <dhcp/dhcp4.h>
20 #include <dhcp/dhcp6.h>
21 #include <dhcp/pkt4.h>
22 #include <dhcp/pkt6.h>
23 
24 #include <boost/noncopyable.hpp>
25 #include <boost/shared_ptr.hpp>
26 #include <boost/date_time/posix_time/posix_time.hpp>
27 
28 #include <string>
29 #include <vector>
30 #include <unordered_map>
31 
32 namespace isc {
33 namespace perfdhcp {
34 
35 /// Default transaction id offset in the packet template.
36 static const size_t DHCPV4_TRANSID_OFFSET = 4;
37 /// Default offset of MAC's last octet in the packet template..
38 static const size_t DHCPV4_RANDOMIZATION_OFFSET = 35;
39 /// Default elapsed time offset in the packet template.
40 static const size_t DHCPV4_ELAPSED_TIME_OFFSET = 8;
41 /// Default server id offset in the packet template.
42 static const size_t DHCPV4_SERVERID_OFFSET = 54;
43 /// Default requested ip offset in the packet template.
44 static const size_t DHCPV4_REQUESTED_IP_OFFSET = 240;
45 /// Default DHCPV6 transaction id offset in t the packet template.
46 static const size_t DHCPV6_TRANSID_OFFSET = 1;
47 /// Default DHCPV6 randomization offset (last octet of DUID)
48 /// in the packet template.
49 static const size_t DHCPV6_RANDOMIZATION_OFFSET = 21;
50 /// Default DHCPV6 elapsed time offset in the packet template.
51 static const size_t DHCPV6_ELAPSED_TIME_OFFSET = 84;
52 /// Default DHCPV6 server id offset in the packet template.
53 static const size_t DHCPV6_SERVERID_OFFSET = 22;
54 /// Default DHCPV6 IA_NA offset in the packet template.
55 static const size_t DHCPV6_IA_NA_OFFSET = 40;
56 
57 /// \brief Test Control class.
58 ///
59 /// This class is used to run the performance test with
60 /// with \ref TestControl::runWrapped function. This function can be executed
61 /// multiple times if desired because it resets TestControl's internal
62 /// state every time it is executed. Prior to running \ref TestControl::runWrapped,
63 /// one must make sure to parse command line options by calling
64 /// \ref CommandOptions::parse. Failing to do this will result in an exception.
65 ///
66 /// The following major stages of the test are performed by this class:
67 /// - set default transaction id and MAC address generators - the generator
68 /// is an object of \ref TestControl::NumberGenerator type and it provides
69 /// the custom randomization algorithms,
70 /// - print command line arguments,
71 /// - register option factory functions which are used to generate DHCP options
72 /// being sent to a server,
73 /// - create the socket for communication with a server,
74 /// - read packet templates if user specified template files with '-T' command
75 /// line option,
76 /// - set the interrupt handler (invoked when ^C is pressed) which makes
77 /// perfdhcp stop gracefully and print the test results before exiting,
78 /// - executes an external command (if specified '-w' option), e.g. if user
79 /// specified -w ./foo in the command line then program will execute
80 /// "./foo start" at the beginning of the test and "./foo stop" when the test
81 /// ends,
82 /// - initialize the Statistics Manager,
83 /// - executes the main loop:
84 ///   - calculate how many packets must be send to satisfy desired rate,
85 ///   - receive incoming packets from the server,
86 ///   - check the exit conditions - terminate the program if the exit criteria
87 ///   are fulfilled, e.g. reached maximum number of packet drops,
88 ///   - send the number of packets appropriate to satisfy the desired rate,
89 ///   - optionally print intermediate reports,
90 /// - print statistics, e.g. achieved rate,
91 /// - optionally print some diagnostics.
92 ///
93 /// With the '-w' command line option user may specify the external application
94 /// or script to be executed. This is executed twice, first when the test starts
95 /// and second time when the test ends. This external script or application must
96 /// accept 'start' and 'stop' arguments. The first time it is called, it is
97 /// called with the argument 'start' and the second time with the argument
98 /// 'stop'.
99 ///
100 /// The application is executed by calling fork() to fork the current perfdhcp
101 /// process and then call execlp() to replace the current process image with
102 /// the new one.
103 ///
104 /// Option factory functions are registered using
105 /// \ref dhcp::LibDHCP::OptionFactoryRegister. Registered factory functions
106 /// provide a way to create options of the same type in the same way.
107 ///  When a new option instance is needed, the corresponding factory
108 /// function is called to create it. This is done by calling
109 /// \ref dhcp::Option::factory with DHCP message type specified as one of
110 ///  parameters. Some of the parameters passed to factory function
111 /// may be ignored (e.g. option buffer).
112 /// Please note that naming convention for factory functions within this
113 /// class is as follows:
114 /// - factoryABC4 - factory function for DHCPv4 option,
115 /// - factoryDEF6 - factory function for DHCPv6 option,
116 /// - factoryGHI - factory function that can be used to create either
117 /// DHCPv4 or DHCPv6 option.
118 class TestControl : public boost::noncopyable {
119 public:
120     /// \brief Default constructor.
121     TestControl(CommandOptions& options, BasePerfSocket& socket);
122 
123     /// Packet template buffer.
124     typedef std::vector<uint8_t> TemplateBuffer;
125     /// Packet template buffers list.
126     typedef std::vector<TemplateBuffer> TemplateBufferCollection;
127 
128     /// @brief Delay the exit by a fixed given time to catch up to all exchanges
129     ///     that were already started.
130     /// @return true if need to wait, false = ok to exit now
131     bool waitToExit();
132 
133     /// @brief Checks if all expected packets were already received
134     bool haveAllPacketsBeenReceived() const;
135 
136     /// \brief Number generator class.
137     ///
138     /// This is default numbers generator class. The member function is
139     /// used to generate uint32_t values. Other generator classes should
140     /// derive from this one to implement generation algorithms
141     /// (e.g. sequential or based on random function).
142     class NumberGenerator {
143     public:
144 
145         /// \brief Destructor.
~NumberGenerator()146         virtual ~NumberGenerator() { }
147 
148         /// \brief Generate number.
149         ///
150         /// \return Generate number.
151         virtual uint32_t generate() = 0;
152     };
153 
154     /// The default generator pointer.
155     typedef boost::shared_ptr<NumberGenerator> NumberGeneratorPtr;
156 
157     /// \brief Sequential numbers generator class.
158     class SequentialGenerator : public NumberGenerator {
159     public:
160         /// \brief Constructor.
161         ///
162         /// \param range maximum number generated. If 0 is given then
163         /// range defaults to maximum uint32_t value.
164         SequentialGenerator(uint32_t range = 0xFFFFFFFF) :
NumberGenerator()165             NumberGenerator(),
166             num_(0),
167             range_(range) {
168             if (range_ == 0) {
169                 range_ = 0xFFFFFFFF;
170             }
171         }
172 
173         /// \brief Generate number sequentially.
174         ///
175         /// \return generated number.
generate()176         virtual uint32_t generate() {
177             uint32_t num = num_;
178             num_ = (num_ + 1) % range_;
179             return (num);
180         }
181     private:
182         uint32_t num_;   ///< Current number.
183         uint32_t range_; ///< Number of unique numbers generated.
184     };
185 
186     /// \brief Length of the Ethernet HW address (MAC) in bytes.
187     ///
188     /// \todo Make this variable length as there are cases when HW
189     /// address is longer than this (e.g. 20 bytes).
190     static const uint8_t HW_ETHER_LEN = 6;
191 
192     /// \brief Set new transaction id generator.
193     ///
194     /// \param generator generator object to be used.
setTransidGenerator(const NumberGeneratorPtr & generator)195     void setTransidGenerator(const NumberGeneratorPtr& generator) {
196         transid_gen_.reset();
197         transid_gen_ = generator;
198     }
199 
200     /// \brief Set new MAC address generator.
201     ///
202     /// Set numbers generator that will be used to generate various
203     /// MAC addresses to simulate number of clients.
204     ///
205     /// \param generator object to be used.
setMacAddrGenerator(const NumberGeneratorPtr & generator)206     void setMacAddrGenerator(const NumberGeneratorPtr& generator) {
207         macaddr_gen_.reset();
208         macaddr_gen_ = generator;
209     }
210 
211     /// \brief Removes cached DHCPv6 Reply packets every second.
212     ///
213     /// This function wipes cached Reply packets from the storage.
214     /// The number of packets left in the storage after the call
215     /// to this function should guarantee that the Renew packets
216     /// can be sent at the given rate. Note that the Renew packets
217     /// are generated for the existing leases, represented here as
218     /// replies from the server.
219     /// @todo Instead of cleaning packets periodically we could
220     /// just stop adding new packets when the certain threshold
221     /// has been reached.
222     void cleanCachedPackets();
223 
224     /// \brief Get interrupted flag.
interrupted()225     bool interrupted() const { return interrupted_; }
226 
227     /// \brief Get stats manager.
getStatsMgr()228     StatsMgr& getStatsMgr() { return stats_mgr_; };
229 
230     /// \brief Start receiver.
start()231     void start() { receiver_.start(); }
232 
233     /// \brief Stop receiver.
stop()234     void stop() { receiver_.stop(); }
235 
236     /// \brief Run wrapped command.
237     ///
238     /// \param do_stop execute wrapped command with "stop" argument.
239     void runWrapped(bool do_stop = false) const;
240 
241     /// \brief Get received server id flag.
serverIdReceived()242     bool serverIdReceived() const { return first_packet_serverid_.size() > 0; }
243 
244     /// \brief Get received server id.
getServerId()245     std::string getServerId() const { return vector2Hex(first_packet_serverid_); }
246 
247     /// \brief Send number of packets to initiate new exchanges.
248     ///
249     /// Method initiates the new DHCP exchanges by sending number
250     /// of DISCOVER (DHCPv4) or SOLICIT (DHCPv6) packets. If preload
251     /// mode was requested sent packets will not be counted in
252     /// the statistics. The responses from the server will be
253     /// received and counted as orphans because corresponding sent
254     /// packets are not included in StatsMgr for match.
255     /// When preload mode is disabled and diagnostics flag 'i' is
256     /// specified then function will be trying to receive late packets
257     /// before new packets are sent to the server. Statistics of
258     /// late received packets is updated accordingly.
259     ///
260     /// \todo do not count responses in preload mode as orphans.
261     ///
262     /// \param packets_num number of packets to be sent.
263     /// \param preload preload mode, packets not included in statistics.
264     /// \throw isc::Unexpected if thrown by packet sending method.
265     /// \throw isc::InvalidOperation if thrown by packet sending method.
266     /// \throw isc::OutOfRange if thrown by packet sending method.
267     void sendPackets(const uint64_t packets_num,
268                      const bool preload = false);
269 
270     /// \brief Send number of DHCPREQUEST (renew) messages to a server.
271     ///
272     /// \param msg_type A type of the messages to be sent (DHCPREQUEST or
273     /// DHCPRELEASE).
274     /// \param msg_num A number of messages to be sent.
275     ///
276     /// \return A number of messages actually sent.
277     uint64_t sendMultipleMessages4(const uint32_t msg_type,
278                                    const uint64_t msg_num);
279 
280     /// \brief Send number of DHCPv6 Renew or Release messages to the server.
281     ///
282     /// \param msg_type A type of the messages to be sent (DHCPV6_RENEW or
283     /// DHCPV6_RELEASE).
284     /// \param msg_num A number of messages to be sent.
285     ///
286     /// \return A number of messages actually sent.
287     uint64_t sendMultipleMessages6(const uint32_t msg_type,
288                                    const uint64_t msg_num);
289 
290     /// \brief Pull packets from receiver and process them.
291     ///
292     /// It runs in a loop until there are no packets in receiver.
293     unsigned int consumeReceivedPackets();
294 
295     /// \brief Print intermediate statistics.
296     ///
297     /// Print brief statistics regarding number of sent packets,
298     /// received packets and dropped packets so far.
299     void printIntermediateStats();
300 
301     /// \brief Print performance statistics.
302     ///
303     /// Method prints performance statistics.
304     /// \throws isc::InvalidOperation if Statistics Manager was
305     /// not initialized.
306     void printStats() const;
307 
308     /// \brief Print templates information.
309     ///
310     /// Method prints information about data offsets
311     /// in packet templates and their contents.
312     void printTemplates() const;
313 
314     /// \brief Get set of unique replied addresses.
getAllUniqueAddrReply()315     std::set<std::string>& getAllUniqueAddrReply() {
316         return unique_reply_address_;
317     }
318 
319     /// \brief Get set of unique advertised addresses.
getAllUniqueAddrAdvert()320     std::set<std::string>& getAllUniqueAddrAdvert() {
321         return unique_address_;
322     }
323 
324     /// \brief Convert binary value to hex string.
325     ///
326     /// \todo Consider moving this function to src/lib/util.
327     ///
328     /// \param b byte to convert.
329     /// \return hex string.
330     static std::string byte2Hex(const uint8_t b);
331 
332     /// \brief Convert vector in hexadecimal string.
333     ///
334     /// \todo Consider moving this function to src/lib/util.
335     ///
336     /// \param vec vector to be converted.
337     /// \param separator separator.
338     static std::string vector2Hex(const std::vector<uint8_t>& vec,
339                                   const std::string& separator = "");
340 
341     /// \brief Initialized at first exit condition with the time perfdhcp
342     ///  should exit
343     boost::posix_time::ptime exit_time_;
344 
345     // We would really like following methods and members to be private but
346     // they have to be accessible for unit-testing. Another, possibly better,
347     // solution is to make this class friend of test class but this is not
348     // what's followed in other classes.
349 protected:
350     /// Generate uniformly distributed integers in range of [min, max]
351     UniformRandomIntegerGenerator number_generator_;
352 
353     /// \brief Creates DHCPREQUEST from a DHCPACK message.
354     ///
355     /// \param ack An instance of the DHCPACK message to be used to
356     /// create a new message.
357     ///
358     /// \return Pointer to the created message.
359     dhcp::Pkt4Ptr createMessageFromAck(const uint16_t msg_type,
360                                        const dhcp::Pkt4Ptr& ack);
361 
362     /// \brief Creates DHCPv6 message from the Reply packet.
363     ///
364     /// This function creates DHCPv6 Renew or Release message using the
365     /// data from the Reply message by copying options from the Reply
366     /// message.
367     ///
368     /// \param msg_type A type of the message to be created.
369     /// \param reply An instance of the Reply packet which contents should
370     /// be used to create an instance of the new message.
371     ///
372     /// \return created Release or Renew message
373     /// \throw isc::BadValue if the msg_type is neither DHCPV6_RENEW nor
374     /// DHCPV6_RELEASE or if the reply is NULL.
375     /// \throw isc::Unexpected if mandatory options are missing in the
376     /// Reply message.
377     dhcp::Pkt6Ptr createMessageFromReply(const uint16_t msg_type,
378                                          const dhcp::Pkt6Ptr& reply);
379 
380     /// \brief Factory function to create DHCPv6 ELAPSED_TIME option.
381     ///
382     /// This factory function creates DHCPv6 ELAPSED_TIME option instance.
383     /// If empty buffer is passed the option buffer will be initialized
384     /// to length 2 and values will be initialized to zeros. Otherwise
385     /// function will initialize option buffer with values in passed buffer.
386     ///
387     /// \param u universe (ignored)
388     /// \param type option-type (ignored).
389     /// \param buf option-buffer containing option content (2 bytes) or
390     /// empty buffer if option content has to be set to default (0) value.
391     /// \throw if elapsed time buffer size is neither 2 nor 0.
392     /// \return instance o the option.
393     static dhcp::OptionPtr
394     factoryElapsedTime6(dhcp::Option::Universe u,
395                         uint16_t type,
396                         const dhcp::OptionBuffer& buf);
397 
398     /// \brief Factory function to create generic option.
399     ///
400     /// This factory function creates option with specified universe,
401     /// type and buf. It does not have any additional logic validating
402     /// the buffer contents, size  etc.
403     ///
404     /// \param u universe (V6 or V4).
405     /// \param type option-type (ignored).
406     /// \param buf option-buffer.
407     /// \return instance o the option.
408     static dhcp::OptionPtr factoryGeneric(dhcp::Option::Universe u,
409                                           uint16_t type,
410                                           const dhcp::OptionBuffer& buf);
411 
412     /// \brief Factory function to create IA_NA option.
413     ///
414     /// This factory function creates DHCPv6 IA_NA option instance.
415     ///
416     /// \todo add support for IA Address options.
417     ///
418     /// \param u universe (ignored).
419     /// \param type option-type (ignored).
420     /// \param buf option-buffer carrying IANA suboptions.
421     /// \return instance of IA_NA option.
422     static dhcp::OptionPtr factoryIana6(dhcp::Option::Universe u,
423                                         uint16_t type,
424                                         const dhcp::OptionBuffer& buf);
425 
426     /// \brief Factory function to create IA_PD option.
427     ///
428     /// this factory function creates DHCPv6 IA_PD option instance.
429     ///
430     /// \param u universe (ignored).
431     /// \param type option-type (ignored).
432     /// \param buf option-buffer carrying sub-options.
433     static dhcp::OptionPtr factoryIapd6(dhcp::Option::Universe u,
434                                         uint16_t type,
435                                         const dhcp::OptionBuffer& buf);
436 
437     /// \brief Factory function to create DHCPv6 ORO option.
438     ///
439     /// This factory function creates DHCPv6 Option Request Option instance.
440     /// The created option will contain the following set of requested options:
441     /// - D6O_NAME_SERVERS
442     /// - D6O_DOMAIN_SEARCH
443     ///
444     /// \param u universe (ignored).
445     /// \param type option-type (ignored).
446     /// \param buf option-buffer (ignored).
447     /// \return instance of ORO option.
448     static dhcp::OptionPtr
449     factoryOptionRequestOption6(dhcp::Option::Universe u,
450                                 uint16_t type,
451                                 const dhcp::OptionBuffer& buf);
452 
453     /// \brief Factory function to create DHCPv6 RAPID_COMMIT option instance.
454     ///
455     /// This factory function creates DHCPv6 RAPID_COMMIT option instance.
456     /// The buffer passed to this option must be empty because option does
457     /// not have any payload.
458     ///
459     /// \param u universe (ignored).
460     /// \param type option-type (ignored).
461     /// \param buf option-buffer (ignored).
462     /// \return instance of RAPID_COMMIT option..
463     static dhcp::OptionPtr factoryRapidCommit6(dhcp::Option::Universe u,
464                                                uint16_t type,
465                                                const dhcp::OptionBuffer& buf);
466 
467 
468     /// \brief Factory function to create DHCPv4 Request List option.
469     ///
470     /// This factory function creates DHCPv4 PARAMETER_REQUEST_LIST option
471     /// instance with the following set of requested options:
472     /// - DHO_SUBNET_MASK,
473     /// - DHO_BROADCAST_ADDRESS,
474     /// - DHO_TIME_OFFSET,
475     /// - DHO_ROUTERS,
476     /// - DHO_DOMAIN_NAME,
477     /// - DHO_DOMAIN_NAME_SERVERS,
478     /// - DHO_HOST_NAME.
479     ///
480     /// \param u universe (ignored).
481     /// \param type option-type (ignored).
482     /// \param buf option-buffer (ignored).
483     /// \return instance o the generic option.
484     static dhcp::OptionPtr factoryRequestList4(dhcp::Option::Universe u,
485                                                uint16_t type,
486                                                const dhcp::OptionBuffer& buf);
487 
488     /// \brief Generate DHCPv4 client identifier from HW address.
489     ///
490     /// This method generates DHCPv4 client identifier option from a
491     /// HW address.
492     ///
493     /// \param hwaddr HW address.
494     ///
495     /// \return Pointer to an instance of the generated option.
496     dhcp::OptionPtr generateClientId(const dhcp::HWAddrPtr& hwaddr) const;
497 
498     /// \brief Generate DUID.
499     ///
500     /// Method generates unique DUID. The number of DUIDs it can generate
501     /// depends on the number of simulated clients, which is specified
502     /// from the command line. It uses \ref CommandOptions object to retrieve
503     /// number of clients. Since the last six octets of DUID are constructed
504     /// from the MAC address, this function uses \ref generateMacAddress
505     /// internally to randomize the DUID.
506     ///
507     /// \todo add support for other types of DUID.
508     ///
509     /// \param [out] randomized number of bytes randomized (initial value
510     /// is ignored).
511     /// \throw isc::BadValue if \ref generateMacAddress throws.
512     /// \return vector representing DUID.
513     std::vector<uint8_t> generateDuid(uint8_t& randomized);
514 
515     /// \brief Generate MAC address.
516     ///
517     /// This method generates MAC address. The number of unique
518     /// MAC addresses it can generate is determined by the number
519     /// simulated DHCP clients specified from command line. It uses
520     /// \ref CommandOptions object to retrieve number of clients.
521     /// Based on this the random value is generated and added to
522     /// the MAC address template (default MAC address).
523     ///
524     /// \param [out] randomized number of bytes randomized (initial
525     /// value is ignored).
526     /// \throw isc::BadValue if MAC address template (default or specified
527     /// from the command line) has invalid size (expected 6 octets).
528     /// \return generated MAC address.
529     std::vector<uint8_t> generateMacAddress(uint8_t& randomized);
530 
531     /// \brief generate transaction id.
532     ///
533     /// Generate transaction id value (32-bit for DHCPv4,
534     /// 24-bit for DHCPv6).
535     ///
536     /// \return generated transaction id.
generateTransid()537     uint32_t generateTransid() {
538         return (transid_gen_->generate());
539     }
540 
541     /// \brief Return template buffer.
542     ///
543     /// Method returns template buffer at specified index.
544     ///
545     /// \param idx index of template buffer.
546     /// \throw isc::OutOfRange if buffer index out of bounds.
547     /// \return reference to template buffer.
548     TemplateBuffer getTemplateBuffer(const size_t idx) const;
549 
550     /// \brief Reads packet templates from files.
551     ///
552     /// Method iterates through all specified template files, reads
553     /// their content and stores it in class internal buffers. Template
554     /// file names are specified from the command line with -T option.
555     ///
556     /// \throw isc::BadValue if any of the template files does not exist,
557     /// contains characters other than hexadecimal digits or spaces.
558     /// \throw OutOfRange if any of the template files is empty or has
559     /// odd number of hexadecimal digits.
560     void initPacketTemplates();
561 
562     /// \brief Print rate statistics.
563     ///
564     /// Method print packet exchange rate statistics.
565     void printRate() const;
566 
567     /// \brief Process received DHCPv4 packet.
568     ///
569     /// Method performs processing of the received DHCPv4 packet,
570     /// updates statistics and responds to the server if required,
571     /// e.g. when OFFER packet arrives, this function will initiate
572     /// REQUEST message to the server.
573     ///
574     /// \warning this method does not check if provided socket is
575     /// valid (specifically if v4 socket for received v4 packet).
576     ///
577     /// \param [in] pkt4 object representing DHCPv4 packet received.
578     /// \throw isc::BadValue if unknown message type received.
579     /// \throw isc::Unexpected if unexpected error occurred.
580     void processReceivedPacket4(const dhcp::Pkt4Ptr& pkt4);
581 
582     /// \brief Process IA in received DHCPv6 packet.
583     ///
584     /// Process IA in received message to check if it contain proper
585     /// address and/or prefix
586     ///
587     /// \param [in] pkt6 object representing DHCPv6 packet received.
588     /// \return true if the message include correct IA, false otherwise.
589     bool validateIA(const dhcp::Pkt6Ptr& pkt6);
590 
591     /// \brief Process received v6 addresses uniqueness.
592     ///
593     /// Generate list of addresses and check for uniqueness.
594     ///
595     /// \param pkt6 object representing received DHCPv6 packet
596     /// \param xchg_type ExchangeType enum value.
597     void address6Uniqueness(const dhcp::Pkt6Ptr& pkt6, ExchangeType xchg_type);
598 
599     /// \brief Process received v4 addresses uniqueness.
600     ///
601     /// Generate list of addresses and check for uniqueness.
602     ///
603     /// \param pkt4 object representing received DHCPv4 packet
604     /// \param xchg_type ExchangeType enum value.
605     void address4Uniqueness(const dhcp::Pkt4Ptr& pkt4, ExchangeType xchg_type);
606 
607     /// \brief add unique address to already assigned list.
608     ///
609     /// Add address and/or prefix to unique set if it's not already there,
610     /// otherwise increment the number of non unique addresses.
611     ///
612     /// \param current set of addresses that should be added to unique list
613     /// \param xchg_type ExchangeType enum value.
addUniqeAddr(const std::set<std::string> & current,ExchangeType xchg_type)614     void addUniqeAddr(const std::set<std::string>& current, ExchangeType xchg_type) {
615         switch(xchg_type) {
616             case ExchangeType::SA: {
617                 for (auto current_it = current.begin();
618                      current_it != current.end(); ++current_it) {
619                     // addresses should be unique cross packets
620                     auto ret = unique_address_.emplace(*current_it);
621                     if (!ret.second) {
622                         stats_mgr_.updateNonUniqueAddrNum(ExchangeType::SA);
623                     }
624                 }
625                 break;
626             }
627             case ExchangeType::RR: {
628                 for (auto current_it = current.begin();
629                      current_it != current.end(); ++current_it) {
630                     // addresses should be unique cross packets
631                     auto ret = unique_reply_address_.emplace(*current_it);
632                     if (!ret.second) {
633                         stats_mgr_.updateNonUniqueAddrNum(ExchangeType::RR);
634                     }
635                 }
636                 break;
637             }
638             case ExchangeType::RLA:
639             case ExchangeType::RL: {
640                 removeUniqueAddr(current);
641                 break;
642             }
643             case ExchangeType::DO: {
644                 for (auto current_it = current.begin();
645                      current_it != current.end(); ++current_it) {
646                     // addresses should be unique cross packets
647                     auto ret = unique_address_.emplace(*current_it);
648                     if (!ret.second) {
649                         stats_mgr_.updateNonUniqueAddrNum(ExchangeType::DO);
650                     }
651                 }
652                 break;
653             }
654             case ExchangeType::RA: {
655                 for (auto current_it = current.begin();
656                      current_it != current.end(); ++current_it) {
657                     // addresses should be unique cross packets
658                     auto ret = unique_reply_address_.emplace(*current_it);
659                     if (!ret.second) {
660                         stats_mgr_.updateNonUniqueAddrNum(ExchangeType::RA);
661                     }
662                 }
663                 break;
664             }
665             case ExchangeType::RNA:
666             case ExchangeType::RN:
667             default:
668                 break;
669         }
670     }
671 
672     /// \brief remove unique address from list.
673     ///
674     /// If address is released we should remove it from both
675     /// advertised (offered) and assigned sets.
676     ///
677     /// \param addr holding value of unique address.
removeUniqueAddr(const std::set<std::string> & addr)678     void removeUniqueAddr(const std::set<std::string>& addr) {
679         for (auto addr_it = addr.begin(); addr_it != addr.end(); ++addr_it) {
680             auto it = unique_address_.find(*addr_it);
681             if (it != unique_address_.end()) {
682                 unique_address_.erase(it);
683             }
684 
685             auto it2 = unique_reply_address_.find(*addr_it);
686             if (it2 != unique_reply_address_.end()) {
687                 unique_reply_address_.erase(it2);
688             }
689         }
690     }
691 
692     /// \brief Process received DHCPv6 packet.
693     ///
694     /// Method performs processing of the received DHCPv6 packet,
695     /// updates statistics and responds to the server if required,
696     /// e.g. when ADVERTISE packet arrives, this function will initiate
697     /// REQUEST message to the server.
698     ///
699     /// \param [in] pkt6 object representing DHCPv6 packet received.
700     /// \throw isc::BadValue if unknown message type received.
701     /// \throw isc::Unexpected if unexpected error occurred.
702     void processReceivedPacket6(const dhcp::Pkt6Ptr& pkt6);
703 
704     /// \brief Register option factory functions for DHCPv4.
705     ///
706     /// Method registers option factory functions for DHCPv4.
707     /// These functions are called to create instances of DHCPv4
708     /// options. Call \ref dhcp::Option::factory to invoke factory
709     /// function for particular option. Don't use this function directly.
710     /// Use \ref registerOptionFactories instead.
711     void registerOptionFactories4() const;
712 
713     /// \brief Register option factory functions for DHCPv6.
714     ///
715     /// Method registers option factory functions for DHCPv6.
716     /// These functions are called to create instances of DHCPv6
717     /// options. Call \ref dhcp::Option::factory to invoke factory
718     /// function for particular option. Don't use this function directly.
719     /// Use \ref registerOptionFactories instead.
720     void registerOptionFactories6() const;
721 
722     /// \brief Register option factory functions for DHCPv4 or DHCPv6.
723     ///
724     /// Method registers option factory functions for DHCPv4 or DHCPv6,
725     /// depending in which mode test is currently running.
726     void registerOptionFactories() const;
727 
728     /// \brief Resets internal state of the object.
729     ///
730     /// Method resets internal state of the object. It has to be
731     /// called before new test is started.
732     void reset();
733 
734     /// \brief Save the first DHCPv4 sent packet of the specified type.
735     ///
736     /// This method saves first packet of the specified being sent
737     /// to the server if user requested diagnostics flag 'T'. In
738     /// such case program has to print contents of selected packets
739     /// being sent to the server. It collects first packets of each
740     /// type and keeps them around until test finishes. Then they
741     /// are printed to the user. If packet of specified type has
742     /// been already stored this function performs no operation.
743     /// This function does not perform sanity check if packet
744     /// pointer is valid. Make sure it is before calling it.
745     ///
746     /// \param pkt packet to be stored.
747     inline void saveFirstPacket(const dhcp::Pkt4Ptr& pkt);
748 
749     /// \brief Save the first DHCPv6 sent packet of the specified type.
750     ///
751     /// This method saves first packet of the specified being sent
752     /// to the server if user requested diagnostics flag 'T'. In
753     /// such case program has to print contents of selected packets
754     /// being sent to the server. It collects first packets of each
755     /// type and keeps them around until test finishes. Then they
756     /// are printed to the user. If packet of specified type has
757     /// been already stored this function performs no operation.
758     /// This function does not perform sanity check if packet
759     /// pointer is valid. Make sure it is before calling it.
760     ///
761     /// \param pkt packet to be stored.
762     inline void saveFirstPacket(const dhcp::Pkt6Ptr& pkt);
763 
764     /// \brief Send DHCPv4 DISCOVER message.
765     ///
766     /// Method creates and sends DHCPv4 DISCOVER message to the server
767     /// with the following options:
768     /// - MESSAGE_TYPE set to DHCPDISCOVER
769     /// - PARAMETER_REQUEST_LIST with the same list of requested options
770     /// as described in \ref factoryRequestList4.
771     /// The transaction id and MAC address are randomly generated for
772     /// the message. Range of unique MAC addresses generated depends
773     /// on the number of clients specified from the command line.
774     /// Copy of sent packet is stored in the stats_mgr_ object to
775     /// update statistics.
776     ///
777     /// \param preload preload mode, packets not included in statistics.
778     ///
779     /// \throw isc::Unexpected if failed to create new packet instance.
780     /// \throw isc::BadValue if MAC address has invalid length.
781     /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
782     void sendDiscover4(const bool preload = false);
783 
784     /// \brief Send DHCPv4 DISCOVER message from template.
785     ///
786     /// Method sends DHCPv4 DISCOVER message from template. The
787     /// template data is expected to be in binary format. Provided
788     /// buffer is copied and parts of it are replaced with actual
789     /// data (e.g. MAC address, transaction id etc.).
790     /// Copy of sent packet is stored in the stats_mgr_ object to
791     /// update statistics.
792     ///
793     /// \param template_buf buffer holding template packet.
794     /// \param preload preload mode, packets not included in statistics.
795     ///
796     /// \throw isc::OutOfRange if randomization offset is out of bounds.
797     /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
798     void sendDiscover4(const std::vector<uint8_t>& template_buf,
799                        const bool preload = false);
800 
801     /// \brief Send DHCPv4 renew (DHCPREQUEST).
802     ///
803     /// \param msg_type A type of the message to be sent (DHCPREQUEST or
804     /// DHCPRELEASE).
805     ///
806     /// \return true if the message has been sent, false otherwise.
807     bool sendMessageFromAck(const uint16_t msg_type);
808 
809     /// \brief Send DHCPv6 Renew or Release message.
810     ///
811     /// This method will select an existing lease from the Reply packet cache
812     /// If there is no lease that can be renewed or released this method will
813     /// return false.
814     ///
815     /// \param msg_type A type of the message to be sent (DHCPV6_RENEW or
816     /// DHCPV6_RELEASE).
817     ///
818     /// \return true if the message has been sent, false otherwise.
819     bool sendMessageFromReply(const uint16_t msg_type);
820 
821     /// \brief Send DHCPv4 REQUEST message.
822     ///
823     /// Method creates and sends DHCPv4 REQUEST message to the server.
824     /// Copy of sent packet is stored in the stats_mgr_ object to
825     /// update statistics.
826     ///
827     /// \param discover_pkt4 DISCOVER packet sent.
828     /// \param offer_pkt4 OFFER packet object.
829     ///
830     /// \throw isc::Unexpected if unexpected error occurred.
831     /// \throw isc::InvalidOperation if Statistics Manager has not been
832     /// initialized.
833     /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
834     void sendRequest4(const dhcp::Pkt4Ptr& discover_pkt4,
835                       const dhcp::Pkt4Ptr& offer_pkt4);
836 
837     /// \brief Send DHCPv4 REQUEST message from template.
838     ///
839     /// Method sends DHCPv4 REQUEST message from template.
840     /// Copy of sent packet is stored in the stats_mgr_ object to
841     /// update statistics.
842     ///
843     /// \param template_buf buffer holding template packet.
844     /// \param discover_pkt4 DISCOVER packet sent.
845     /// \param offer_pkt4 OFFER packet received.
846     ///
847     /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
848     void sendRequest4(const std::vector<uint8_t>& template_buf,
849                       const dhcp::Pkt4Ptr& discover_pkt4,
850                       const dhcp::Pkt4Ptr& offer_pkt4);
851 
852     /// \brief Send DHCPv6 REQUEST message.
853     ///
854     /// Method creates and sends DHCPv6 REQUEST message to the server
855     /// with the following options:
856     /// - D6O_ELAPSED_TIME
857     /// - D6O_CLIENTID
858     /// - D6O_SERVERID
859     /// Copy of sent packet is stored in the stats_mgr_ object to
860     /// update statistics.
861     ///
862     /// \param advertise_pkt6 ADVERTISE packet object.
863     /// \throw isc::Unexpected if unexpected error occurred.
864     /// \throw isc::InvalidOperation if Statistics Manager has not been
865     /// initialized.
866     ///
867     /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
868     void sendRequest6(const dhcp::Pkt6Ptr& advertise_pkt6);
869 
870     /// \brief Send DHCPv6 REQUEST message from template.
871     ///
872     /// Method sends DHCPv6 REQUEST message from template.
873     /// Copy of sent packet is stored in the stats_mgr_ object to
874     /// update statistics.
875     ///
876     /// \param template_buf packet template buffer.
877     /// \param advertise_pkt6 ADVERTISE packet object.
878     ///
879     /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
880     void sendRequest6(const std::vector<uint8_t>& template_buf,
881                       const dhcp::Pkt6Ptr& advertise_pkt6);
882 
883     /// \brief Send DHCPv6 SOLICIT message.
884     ///
885     /// Method creates and sends DHCPv6 SOLICIT message to the server
886     /// with the following options:
887     /// - D6O_ELAPSED_TIME,
888     /// - D6O_RAPID_COMMIT if rapid commit is requested in command line,
889     /// - D6O_CLIENTID,
890     /// - D6O_ORO (Option Request Option),
891     /// - D6O_IA_NA.
892     /// Copy of sent packet is stored in the stats_mgr_ object to
893     /// update statistics.
894     ///
895     /// \param preload mode, packets not included in statistics.
896     ///
897     /// \throw isc::Unexpected if failed to create new packet instance.
898     /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
899     void sendSolicit6(const bool preload = false);
900 
901     /// \brief Send DHCPv6 SOLICIT message from template.
902     ///
903     /// Method sends DHCPv6 SOLICIT message from template.
904     /// Copy of sent packet is stored in the stats_mgr_ object to
905     /// update statistics.
906     ///
907     /// \param template_buf packet template buffer.
908     /// \param preload mode, packets not included in statistics.
909     ///
910     /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
911     void sendSolicit6(const std::vector<uint8_t>& template_buf,
912                       const bool preload = false);
913 
914     /// \brief Set default DHCPv4 packet parameters.
915     ///
916     /// This method sets default parameters on the DHCPv4 packet:
917     /// - interface name,
918     /// - local port = 68 (DHCP client port),
919     /// - remote port = 67 (DHCP server port),
920     /// - server's address,
921     /// - GIADDR = local address where socket is bound to,
922     /// - hops = 1 (pretending that we are a relay)
923     ///
924     /// \param pkt reference to packet to be configured.
925     void setDefaults4(const dhcp::Pkt4Ptr& pkt);
926 
927     /// \brief Set default DHCPv6 packet parameters.
928     ///
929     /// This method sets default parameters on the DHCPv6 packet:
930     /// - interface name,
931     /// - interface index,
932     /// - local port,
933     /// - remote port,
934     /// - local address,
935     /// - remote address (server).
936     ///
937     /// \param pkt reference to packet to be configured.
938     void setDefaults6(const dhcp::Pkt6Ptr& pkt);
939 
940     /// @brief Inserts extra options specified by user.
941     ///
942     /// Note: addExtraOpts for v4 and v6 could easily be turned into a template.
943     /// However, this would require putting code here that uses CommandOptions,
944     /// and that would create dependency between test_control.h and
945     /// command_options.h.
946     ///
947     /// @param pkt4 options will be added here.
948     void addExtraOpts(const dhcp::Pkt4Ptr& pkt4);
949 
950     /// @brief Inserts extra options specified by user.
951     ///
952     /// Note: addExtraOpts for v4 and v6 could easily be turned into a template.
953     /// However, this would require putting code here that uses CommandOptions,
954     /// and that would create dependency between test_control.h and
955     /// command_options.h.
956     ///
957     /// @param pkt6 options will be added here.
958     void addExtraOpts(const dhcp::Pkt6Ptr& pkt6);
959 
960     /// \brief Copies IA_NA or IA_PD option from one packet to another.
961     ///
962     /// This function checks the lease-type specified in the command line
963     /// with option -e<lease-type>. If 'address-only' value has been specified
964     /// this function expects that IA_NA option is present in the packet
965     /// encapsulated by pkt_from object. If 'prefix-only' value has been
966     /// specified, this function expects that IA_PD option is present in the
967     /// packet encapsulated by pkt_to object.
968     ///
969     /// \param [in] pkt_from A packet from which options should be copied.
970     /// \param [out] pkt_to A packet to which options should be copied.
971     ///
972     /// \throw isc::NotFound if a required option is not found in the
973     /// packet from which options should be copied.
974     /// \throw isc::BadValue if any of the specified pointers to packets
975     /// is NULL.
976     void copyIaOptions(const dhcp::Pkt6Ptr& pkt_from, dhcp::Pkt6Ptr& pkt_to);
977 
978     /// \brief Calculate elapsed time between two packets.
979     ///
980     /// This function calculates the time elapsed between two packets. If
981     /// the timestamp of the pkt2 is greater than timestamp of the pkt1,
982     /// the positive value is returned. If the pkt2 timestamp is equal or
983     /// less than pkt1 timestamp, 0 is returned.
984     ///
985     /// \tparam T Pkt4Ptr or Pkt6Ptr class.
986     /// \param pkt1 first packet.
987     /// \param pkt2 second packet.
988     /// \throw InvalidOperation if packet timestamps are invalid.
989     /// \return elapsed time in milliseconds between pkt1 and pkt2.
990     template<class T>
991     uint32_t getElapsedTime(const T& pkt1, const T& pkt2);
992 
993     /// \brief Return elapsed time offset in a packet.
994     ///
995     /// \return elapsed time offset in packet.
996     int getElapsedTimeOffset() const;
997 
998     /// \brief Return randomization offset in a packet.
999     ///
1000     /// \return randomization offset in packet.
1001     int getRandomOffset(const int arg_idx) const;
1002 
1003     /// \brief Return requested ip offset in a packet.
1004     ///
1005     /// \return randomization offset in a packet.
1006     int getRequestedIpOffset() const;
1007 
1008     /// \brief Return server id offset in a packet.
1009     ///
1010     /// \return server id offset in packet.
1011     int getServerIdOffset() const;
1012 
1013     /// \brief Return transaction id offset in a packet.
1014     ///
1015     /// \param arg_idx command line argument index to be used.
1016     /// If multiple -X parameters specified it points to the
1017     /// one to be used.
1018     /// \return transaction id offset in packet.
1019     int getTransactionIdOffset(const int arg_idx) const;
1020 
1021     /// \brief Handle child signal.
1022     ///
1023     /// Function handles child signal by waiting for
1024     /// the process to complete.
1025     ///
1026     /// \param sig signal (ignored).
1027     static void handleChild(int sig);
1028 
1029     /// \brief Handle interrupt signal.
1030     ///
1031     /// Function sets flag indicating that program has been
1032     /// interrupted.
1033     ///
1034     /// \param sig signal (ignored).
1035     static void handleInterrupt(int sig);
1036 
1037     /// \brief Print main diagnostics data.
1038     ///
1039     /// Method prints main diagnostics data.
1040     void printDiagnostics() const;
1041 
1042     /// \brief Print template information.
1043     ///
1044     /// \param packet_type packet type.
1045     void printTemplate(const uint8_t packet_type) const;
1046 
1047     /// \brief Read DHCP message template from file.
1048     ///
1049     /// Method reads DHCP message template from file and
1050     /// converts it to binary format. Read data is appended
1051     /// to template_buffers_ vector.
1052     ///
1053     /// \param file_name name of the packet template file.
1054     /// \throw isc::OutOfRange if file is empty or has odd number
1055     /// of hexadecimal digits.
1056     /// \throw isc::BadValue if file contains characters other than
1057     /// spaces or hexadecimal digits.
1058     void readPacketTemplate(const std::string& file_name);
1059 
1060     /// \brief Keep addresses and prefixes from advertise msg for uniqueness checks.
1061     std::set<std::string> unique_address_;
1062 
1063     /// \brief Keep addresses and prefixes from reply msg for uniqueness checks.
1064     std::set<std::string> unique_reply_address_;
1065 
1066     /// \brief Socket used for DHCP traffic.
1067     BasePerfSocket &socket_;
1068 
1069     /// \brief Receiver used to receive DHCP traffic.
1070     Receiver receiver_;
1071 
1072     /// \brief Last intermediate report time.
1073     boost::posix_time::ptime last_report_;
1074 
1075     /// \brief Statistics Manager.
1076     StatsMgr stats_mgr_;
1077 
1078     /// \brief Storage for DHCPACK messages.
1079     PacketStorage<dhcp::Pkt4> ack_storage_;
1080 
1081     /// \brief Storage for reply messages.
1082     PacketStorage<dhcp::Pkt6> reply_storage_;
1083 
1084     /// \brief Transaction id generator.
1085     NumberGeneratorPtr transid_gen_;
1086 
1087     /// \brief Numbers generator for MAC address.
1088     NumberGeneratorPtr macaddr_gen_;
1089 
1090     /// \brief Buffer holding server id received in first packet
1091     dhcp::OptionBuffer first_packet_serverid_;
1092 
1093     /// \brief Packet template buffers.
1094     TemplateBufferCollection template_buffers_;
1095 
1096     /// First packets send. They are used at the end of the test
1097     /// to print packet templates when diagnostics flag T is specified.
1098 
1099     /// \brief Template for v4.
1100     std::map<uint8_t, dhcp::Pkt4Ptr> template_packets_v4_;
1101 
1102     /// \brief Template for v6.
1103     std::map<uint8_t, dhcp::Pkt6Ptr> template_packets_v6_;
1104 
1105     /// \brief Program interrupted flag.
1106     static bool interrupted_;
1107 
1108     /// \brief Command options.
1109     CommandOptions& options_;
1110 };
1111 
1112 }  // namespace perfdhcp
1113 }  // namespace isc
1114 
1115 #endif // TEST_CONTROL_H
1116