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 #include <config.h>
8 
9 #include <perfdhcp/test_control.h>
10 #include <perfdhcp/receiver.h>
11 #include <perfdhcp/command_options.h>
12 #include <perfdhcp/perf_pkt4.h>
13 #include <perfdhcp/perf_pkt6.h>
14 
15 #include <exceptions/exceptions.h>
16 #include <dhcp/libdhcp++.h>
17 #include <dhcp/iface_mgr.h>
18 #include <dhcp/dhcp4.h>
19 #include <dhcp/option6_ia.h>
20 #include <dhcp/option6_iaaddr.h>
21 #include <dhcp/option6_iaprefix.h>
22 #include <dhcp/option_int.h>
23 #include <util/unittests/check_valgrind.h>
24 
25 #include <boost/date_time/posix_time/posix_time.hpp>
26 #include <algorithm>
27 #include <fstream>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <stdint.h>
31 #include <unistd.h>
32 #include <signal.h>
33 #include <sstream>
34 #include <sys/wait.h>
35 
36 using namespace std;
37 using namespace boost::posix_time;
38 using namespace isc;
39 using namespace isc::dhcp;
40 using namespace isc::asiolink;
41 
42 namespace isc {
43 namespace perfdhcp {
44 
45 bool TestControl::interrupted_ = false;
46 
47 bool
waitToExit()48 TestControl::waitToExit() {
49     uint32_t const wait_time = options_.getExitWaitTime();
50 
51     // If we care and not all packets are in yet
52     if (wait_time && !haveAllPacketsBeenReceived()) {
53         const ptime now = microsec_clock::universal_time();
54 
55         // Init the end time if it hasn't started yet
56         if (exit_time_.is_not_a_date_time()) {
57             exit_time_ = now + time_duration(microseconds(wait_time));
58         }
59 
60         // If we're not at end time yet, return true
61         return (now < exit_time_);
62     }
63 
64     // No need to wait, return false;
65     return (false);
66 }
67 
68 bool
haveAllPacketsBeenReceived() const69 TestControl::haveAllPacketsBeenReceived() const {
70     const uint8_t& ipversion = options_.getIpVersion();
71     const std::vector<int>& num_request = options_.getNumRequests();
72     const size_t& num_request_size = num_request.size();
73 
74     if (num_request_size == 0) {
75         return false;
76     }
77 
78     uint32_t responses = 0;
79     uint32_t requests = num_request[0];
80     if (num_request_size >= 2) {
81         requests += num_request[1];
82     }
83 
84     if (ipversion == 4) {
85         responses = stats_mgr_.getRcvdPacketsNum(ExchangeType::DO) +
86                     stats_mgr_.getRcvdPacketsNum(ExchangeType::RA);
87     } else {
88         responses = stats_mgr_.getRcvdPacketsNum(ExchangeType::SA) +
89                     stats_mgr_.getRcvdPacketsNum(ExchangeType::RR);
90     }
91 
92     return (responses == requests);
93 }
94 
95 void
cleanCachedPackets()96 TestControl::cleanCachedPackets() {
97     // When Renews are not sent, Reply packets are not cached so there
98     // is nothing to do.
99     if (options_.getRenewRate() == 0) {
100         return;
101     }
102 
103     static boost::posix_time::ptime last_clean =
104         microsec_clock::universal_time();
105 
106     // Check how much time has passed since last cleanup.
107     time_period time_since_clean(last_clean,
108                                  microsec_clock::universal_time());
109     // Cleanup every 1 second.
110     if (time_since_clean.length().total_seconds() >= 1) {
111         // Calculate how many cached packets to remove. Actually we could
112         // just leave enough packets to handle Renews for 1 second but
113         // since we want to randomize leases to be renewed so leave 5
114         // times more packets to randomize from.
115         /// @todo The cache size might be controlled from the command line.
116         if (reply_storage_.size() > 5 * options_.getRenewRate()) {
117             reply_storage_.clear(reply_storage_.size() -
118                                  5 * options_.getRenewRate());
119         }
120         // Remember when we performed a cleanup for the last time.
121         // We want to do the next cleanup not earlier than in one second.
122         last_clean = microsec_clock::universal_time();
123     }
124 }
125 
126 void
copyIaOptions(const Pkt6Ptr & pkt_from,Pkt6Ptr & pkt_to)127 TestControl::copyIaOptions(const Pkt6Ptr& pkt_from, Pkt6Ptr& pkt_to) {
128     if (!pkt_from || !pkt_to) {
129         isc_throw(BadValue, "NULL pointers must not be specified as arguments"
130                   " for the copyIaOptions function");
131     }
132     // IA_NA
133     if (options_.getLeaseType()
134         .includes(CommandOptions::LeaseType::ADDRESS)) {
135         OptionPtr option = pkt_from->getOption(D6O_IA_NA);
136         if (!option) {
137             isc_throw(NotFound, "IA_NA option not found in the"
138                       " server's response");
139         }
140         pkt_to->addOption(option);
141     }
142     // IA_PD
143     if (options_.getLeaseType()
144         .includes(CommandOptions::LeaseType::PREFIX)) {
145         OptionPtr option = pkt_from->getOption(D6O_IA_PD);
146         if (!option) {
147             isc_throw(NotFound, "IA_PD option not found in the"
148                       " server's response");
149         }
150         pkt_to->addOption(option);
151     }
152 }
153 
154 std::string
byte2Hex(const uint8_t b)155 TestControl::byte2Hex(const uint8_t b) {
156     const int b1 = b / 16;
157     const int b0 = b % 16;
158     ostringstream stream;
159     stream << std::hex << b1 << b0 << std::dec;
160     return (stream.str());
161 }
162 
163 Pkt4Ptr
createMessageFromAck(const uint16_t msg_type,const dhcp::Pkt4Ptr & ack)164 TestControl::createMessageFromAck(const uint16_t msg_type,
165                                   const dhcp::Pkt4Ptr& ack) {
166     // Restrict messages to Release and Renew.
167     if (msg_type != DHCPREQUEST && msg_type != DHCPRELEASE) {
168         isc_throw(isc::BadValue, "invalid message type " << msg_type
169                   << " to be created from Reply, expected DHCPREQUEST or"
170                   " DHCPRELEASE");
171     }
172 
173     // Get the string representation of the message - to be used for error
174     // logging purposes.
175     auto msg_type_str = [=]() -> const char* {
176         return (msg_type == DHCPREQUEST ? "Request" : "Release");
177     };
178 
179     if (!ack) {
180         isc_throw(isc::BadValue, "Unable to create "
181                                      << msg_type_str()
182                                      << " from a null DHCPACK message");
183     } else if (ack->getYiaddr().isV4Zero()) {
184         isc_throw(isc::BadValue,
185                   "Unable to create "
186                       << msg_type_str()
187                       << " from a DHCPACK message containing yiaddr of 0");
188     }
189     Pkt4Ptr msg(new Pkt4(msg_type, generateTransid()));
190     msg->setCiaddr(ack->getYiaddr());
191     msg->setHWAddr(ack->getHWAddr());
192     msg->addOption(generateClientId(msg->getHWAddr()));
193     if (msg_type == DHCPRELEASE) {
194         // RFC 2132: DHCPRELEASE MUST include server ID.
195         if (options_.isUseFirst()) {
196             // Honor the '-1' flag if it exists.
197             if (first_packet_serverid_.empty()) {
198                 isc_throw(isc::BadValue,
199                           "Unable to create "
200                               << msg_type_str()
201                               << "from the first packet which lacks the server "
202                                  "identifier option");
203             }
204             msg->addOption(Option::factory(Option::V4,
205                                            DHO_DHCP_SERVER_IDENTIFIER,
206                                            first_packet_serverid_));
207         } else {
208             // Otherwise take it from the DHCPACK message.
209             OptionPtr server_identifier(
210                 ack->getOption(DHO_DHCP_SERVER_IDENTIFIER));
211             if (!server_identifier) {
212                 isc_throw(isc::BadValue,
213                           "Unable to create "
214                               << msg_type_str()
215                               << "from a DHCPACK message without the server "
216                                  "identifier option");
217             }
218             msg->addOption(server_identifier);
219         }
220     }
221     return (msg);
222 }
223 
224 Pkt6Ptr
createMessageFromReply(const uint16_t msg_type,const dhcp::Pkt6Ptr & reply)225 TestControl::createMessageFromReply(const uint16_t msg_type,
226                                     const dhcp::Pkt6Ptr& reply) {
227     // Restrict messages to Release and Renew.
228     if (msg_type != DHCPV6_RENEW && msg_type != DHCPV6_RELEASE) {
229         isc_throw(isc::BadValue, "invalid message type " << msg_type
230                   << " to be created from Reply, expected DHCPV6_RENEW or"
231                   " DHCPV6_RELEASE");
232     }
233 
234     // Get the string representation of the message - to be used for error
235     // logging purposes.
236     auto msg_type_str = [=]() -> const char* {
237         return (msg_type == DHCPV6_RENEW ? "Renew" : "Release");
238     };
239 
240     // Reply message must be specified.
241     if (!reply) {
242         isc_throw(isc::BadValue, "Unable to create " << msg_type_str()
243                   << " message from the Reply message because the instance of"
244                   " the Reply message is NULL");
245     }
246 
247     Pkt6Ptr msg(new Pkt6(msg_type, generateTransid()));
248     // Client id.
249     OptionPtr opt_clientid = reply->getOption(D6O_CLIENTID);
250     if (!opt_clientid) {
251         isc_throw(isc::Unexpected, "failed to create " << msg_type_str()
252                   << " message because client id option has not been found"
253                   " in the Reply message");
254     }
255     msg->addOption(opt_clientid);
256     // Server id.
257     OptionPtr opt_serverid = reply->getOption(D6O_SERVERID);
258     if (!opt_serverid) {
259         isc_throw(isc::Unexpected, "failed to create " << msg_type_str()
260                   << " because server id option has not been found in the"
261                   " Reply message");
262     }
263     msg->addOption(opt_serverid);
264     copyIaOptions(reply, msg);
265     return (msg);
266 }
267 
268 OptionPtr
factoryElapsedTime6(Option::Universe,uint16_t,const OptionBuffer & buf)269 TestControl::factoryElapsedTime6(Option::Universe, uint16_t,
270                                  const OptionBuffer& buf) {
271     if (buf.size() == 2) {
272         return (OptionPtr(new Option(Option::V6, D6O_ELAPSED_TIME, buf)));
273     } else if (buf.size() == 0) {
274         return (OptionPtr(new Option(Option::V6, D6O_ELAPSED_TIME,
275                                      OptionBuffer(2, 0))));
276     }
277     isc_throw(isc::BadValue,
278               "elapsed time option buffer size has to be 0 or 2");
279 }
280 
281 OptionPtr
factoryGeneric(Option::Universe u,uint16_t type,const OptionBuffer & buf)282 TestControl::factoryGeneric(Option::Universe u, uint16_t type,
283                             const OptionBuffer& buf) {
284     OptionPtr opt(new Option(u, type, buf));
285     return (opt);
286 }
287 
288 OptionPtr
factoryIana6(Option::Universe,uint16_t,const OptionBuffer & buf)289 TestControl::factoryIana6(Option::Universe, uint16_t,
290                           const OptionBuffer& buf) {
291     /// @todo allow different values of T1, T2 and IAID.
292     const uint8_t buf_array[] = {
293         0, 0, 0, 1,                     // IAID = 1
294         0, 0, 3600 >> 8, 3600 & 0xff,  // T1 = 3600
295         0, 0, 5400 >> 8, 5400 & 0xff,   // T2 = 5400
296     };
297     OptionBuffer buf_ia_na(buf_array, buf_array + sizeof(buf_array));
298     for (size_t i = 0;  i < buf.size(); ++i) {
299         buf_ia_na.push_back(buf[i]);
300     }
301     return (OptionPtr(new Option(Option::V6, D6O_IA_NA, buf_ia_na)));
302 }
303 
304 OptionPtr
factoryIapd6(Option::Universe,uint16_t,const OptionBuffer & buf)305 TestControl::factoryIapd6(Option::Universe, uint16_t,
306                           const OptionBuffer& buf) {
307     /// @todo allow different values of T1, T2 and IAID.
308     static const uint8_t buf_array[] = {
309         0, 0, 0, 1,                     // IAID = 1
310         0, 0, 3600 >> 8, 3600 & 0xff,   // T1 = 3600
311         0, 0, 5400 >> 8, 5400 & 0xff,   // T2 = 5400
312     };
313     OptionBuffer buf_ia_pd(buf_array, buf_array + sizeof(buf_array));
314     // Append sub-options to IA_PD.
315     buf_ia_pd.insert(buf_ia_pd.end(), buf.begin(), buf.end());
316     return (OptionPtr(new Option(Option::V6, D6O_IA_PD, buf_ia_pd)));
317 }
318 
319 
320 OptionPtr
factoryRapidCommit6(Option::Universe,uint16_t,const OptionBuffer &)321 TestControl::factoryRapidCommit6(Option::Universe, uint16_t,
322                                  const OptionBuffer&) {
323     return (OptionPtr(new Option(Option::V6, D6O_RAPID_COMMIT, OptionBuffer())));
324 }
325 
326 OptionPtr
factoryOptionRequestOption6(Option::Universe,uint16_t,const OptionBuffer &)327 TestControl::factoryOptionRequestOption6(Option::Universe,
328                                          uint16_t,
329                                          const OptionBuffer&) {
330     const uint8_t buf_array[] = {
331         0, D6O_NAME_SERVERS,
332         0, D6O_DOMAIN_SEARCH,
333     };
334     OptionBuffer buf_with_options(buf_array, buf_array + sizeof(buf_array));
335     return (OptionPtr(new Option(Option::V6, D6O_ORO, buf_with_options)));
336 }
337 
338 
339 OptionPtr
factoryRequestList4(Option::Universe u,uint16_t type,const OptionBuffer & buf)340 TestControl::factoryRequestList4(Option::Universe u,
341                                  uint16_t type,
342                                  const OptionBuffer& buf) {
343     const uint8_t buf_array[] = {
344         DHO_SUBNET_MASK,
345         DHO_BROADCAST_ADDRESS,
346         DHO_TIME_OFFSET,
347         DHO_ROUTERS,
348         DHO_DOMAIN_NAME,
349         DHO_DOMAIN_NAME_SERVERS,
350         DHO_HOST_NAME
351     };
352 
353     OptionBuffer buf_with_options(buf_array, buf_array + sizeof(buf_array));
354     OptionPtr opt(new Option(u, type, buf));
355     opt->setData(buf_with_options.begin(), buf_with_options.end());
356     return (opt);
357 }
358 
359 std::vector<uint8_t>
generateMacAddress(uint8_t & randomized)360 TestControl::generateMacAddress(uint8_t& randomized) {
361     const CommandOptions::MacAddrsVector& macs = options_.getMacsFromFile();
362     // if we are using the -M option return a random one from the list...
363     if (macs.size() > 0) {
364       uint16_t r = number_generator_();
365       if (r >= macs.size()) {
366         r = 0;
367       }
368       return macs[r];
369 
370     } else {
371       // ... otherwise use the standard behavior
372       uint32_t clients_num = options_.getClientsNum();
373       if (clients_num < 2) {
374           return (options_.getMacTemplate());
375       }
376       // Get the base MAC address. We are going to randomize part of it.
377       std::vector<uint8_t> mac_addr(options_.getMacTemplate());
378       if (mac_addr.size() != HW_ETHER_LEN) {
379           isc_throw(BadValue, "invalid MAC address template specified");
380       }
381       uint32_t r = macaddr_gen_->generate();
382       randomized = 0;
383       // Randomize MAC address octets.
384       for (std::vector<uint8_t>::iterator it = mac_addr.end() - 1;
385            it >= mac_addr.begin();
386            --it) {
387           // Add the random value to the current octet.
388           (*it) += r;
389           ++randomized;
390           if (r < 256) {
391               // If we are here it means that there is no sense
392               // to randomize the remaining octets of MAC address
393               // because the following bytes of random value
394               // are zero and it will have no effect.
395               break;
396           }
397           // Randomize the next octet with the following
398           // byte of random value.
399           r >>= 8;
400       }
401       return (mac_addr);
402     }
403 }
404 
405 OptionPtr
generateClientId(const dhcp::HWAddrPtr & hwaddr) const406 TestControl::generateClientId(const dhcp::HWAddrPtr& hwaddr) const {
407     std::vector<uint8_t> client_id(1, static_cast<uint8_t>(hwaddr->htype_));
408     client_id.insert(client_id.end(), hwaddr->hwaddr_.begin(),
409                      hwaddr->hwaddr_.end());
410     return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
411                                  client_id)));
412 }
413 
414 std::vector<uint8_t>
generateDuid(uint8_t & randomized)415 TestControl::generateDuid(uint8_t& randomized) {
416     std::vector<uint8_t> mac_addr(generateMacAddress(randomized));
417     const CommandOptions::MacAddrsVector& macs = options_.getMacsFromFile();
418     // pick a random mac address if we are using option -M..
419     if (macs.size() > 0) {
420       uint16_t r = number_generator_();
421       if (r >= macs.size()) {
422         r = 0;
423       }
424       std::vector<uint8_t> mac = macs[r];
425       // DUID_LL is in this format
426       //  0                   1                   2                   3
427       //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
428       // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
429       // |               3               |    hardware type (16 bits)    |
430       // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
431       // .                                                               .
432       // .             link-layer address (variable length)              .
433       // .                                                               .
434       // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
435 
436       // No C++11 so initializer list support, building a vector<uint8_t> is a
437       // pain...
438       uint8_t duid_ll[] = {0, 3, 0, 1, 0, 0, 0, 0, 0, 0};
439       // copy duid_ll array into the vector
440       std::vector<uint8_t> duid(duid_ll,
441                                 duid_ll + sizeof(duid_ll) / sizeof(duid_ll[0]));
442       // put the mac address bytes at the end
443       std::copy(mac.begin(), mac.end(), duid.begin() + 4);
444       return (duid);
445     } else {
446       uint32_t clients_num = options_.getClientsNum();
447       if ((clients_num == 0) || (clients_num == 1)) {
448           return (options_.getDuidTemplate());
449       }
450       // Get the base DUID. We are going to randomize part of it.
451       std::vector<uint8_t> duid(options_.getDuidTemplate());
452       /// @todo: add support for DUIDs of different sizes.
453       duid.resize(duid.size());
454       std::copy(mac_addr.begin(), mac_addr.end(),
455                 duid.begin() + duid.size() - mac_addr.size());
456       return (duid);
457     }
458 }
459 
460 int
getElapsedTimeOffset() const461 TestControl::getElapsedTimeOffset() const {
462     int elp_offset = options_.getIpVersion() == 4 ?
463         DHCPV4_ELAPSED_TIME_OFFSET : DHCPV6_ELAPSED_TIME_OFFSET;
464     if (options_.getElapsedTimeOffset() > 0) {
465         elp_offset = options_.getElapsedTimeOffset();
466     }
467     return (elp_offset);
468 }
469 
470 template<class T>
471 uint32_t
getElapsedTime(const T & pkt1,const T & pkt2)472 TestControl::getElapsedTime(const T& pkt1, const T& pkt2) {
473     using namespace boost::posix_time;
474     ptime pkt1_time = pkt1->getTimestamp();
475     ptime pkt2_time = pkt2->getTimestamp();
476     if (pkt1_time.is_not_a_date_time() ||
477         pkt2_time.is_not_a_date_time()) {
478         isc_throw(InvalidOperation, "packet timestamp not set");;
479     }
480     time_period elapsed_period(pkt1_time, pkt2_time);
481     return (elapsed_period.is_null() ? 0 :
482             elapsed_period.length().total_milliseconds());
483 }
484 
485 int
getRandomOffset(const int arg_idx) const486 TestControl::getRandomOffset(const int arg_idx) const {
487     int rand_offset = options_.getIpVersion() == 4 ?
488         DHCPV4_RANDOMIZATION_OFFSET : DHCPV6_RANDOMIZATION_OFFSET;
489     if (options_.getRandomOffset().size() > arg_idx) {
490         rand_offset = options_.getRandomOffset()[arg_idx];
491     }
492     return (rand_offset);
493 }
494 
495 int
getRequestedIpOffset() const496 TestControl::getRequestedIpOffset() const {
497     int rip_offset = options_.getIpVersion() == 4 ?
498         DHCPV4_REQUESTED_IP_OFFSET : DHCPV6_IA_NA_OFFSET;
499     if (options_.getRequestedIpOffset() > 0) {
500         rip_offset = options_.getRequestedIpOffset();
501     }
502     return (rip_offset);
503 }
504 
505 int
getServerIdOffset() const506 TestControl::getServerIdOffset() const {
507     int srvid_offset = options_.getIpVersion() == 4 ?
508         DHCPV4_SERVERID_OFFSET : DHCPV6_SERVERID_OFFSET;
509     if (options_.getServerIdOffset() > 0) {
510         srvid_offset = options_.getServerIdOffset();
511     }
512     return (srvid_offset);
513 }
514 
515 TestControl::TemplateBuffer
getTemplateBuffer(const size_t idx) const516 TestControl::getTemplateBuffer(const size_t idx) const {
517     if (template_buffers_.size() > idx) {
518         return (template_buffers_[idx]);
519     }
520     isc_throw(OutOfRange, "invalid buffer index");
521 }
522 
523 int
getTransactionIdOffset(const int arg_idx) const524 TestControl::getTransactionIdOffset(const int arg_idx) const {
525     int xid_offset = options_.getIpVersion() == 4 ?
526         DHCPV4_TRANSID_OFFSET : DHCPV6_TRANSID_OFFSET;
527     if (options_.getTransactionIdOffset().size() > arg_idx) {
528         xid_offset = options_.getTransactionIdOffset()[arg_idx];
529     }
530     return (xid_offset);
531 }
532 
533 void
handleChild(int)534 TestControl::handleChild(int) {
535     int status = 0;
536     while (wait3(&status, WNOHANG, NULL) > 0) {
537         // continue
538     }
539 }
540 
541 void
handleInterrupt(int)542 TestControl::handleInterrupt(int) {
543     interrupted_ = true;
544 }
545 
546 void
initPacketTemplates()547 TestControl::initPacketTemplates() {
548     template_packets_v4_.clear();
549     template_packets_v6_.clear();
550     template_buffers_.clear();
551     std::vector<std::string> template_files = options_.getTemplateFiles();
552     for (std::vector<std::string>::const_iterator it = template_files.begin();
553          it != template_files.end(); ++it) {
554         readPacketTemplate(*it);
555     }
556 }
557 
558 void
sendPackets(const uint64_t packets_num,const bool preload)559 TestControl::sendPackets(const uint64_t packets_num,
560                          const bool preload /* = false */) {
561     for (uint64_t i = packets_num; i > 0; --i) {
562         if (options_.getIpVersion() == 4) {
563             // No template packets means that no -T option was specified.
564             // We have to build packets ourselves.
565             if (template_buffers_.empty()) {
566                 sendDiscover4(preload);
567             } else {
568                 /// @todo add defines for packet type index that can be
569                 /// used to access template_buffers_.
570                 sendDiscover4(template_buffers_[0], preload);
571             }
572         } else {
573             // No template packets means that no -T option was specified.
574             // We have to build packets ourselves.
575             if (template_buffers_.empty()) {
576                 sendSolicit6(preload);
577             } else {
578                 /// @todo add defines for packet type index that can be
579                 /// used to access template_buffers_.
580                 sendSolicit6(template_buffers_[0], preload);
581             }
582         }
583     }
584 }
585 
586 uint64_t
sendMultipleMessages4(const uint32_t msg_type,const uint64_t msg_num)587 TestControl::sendMultipleMessages4(const uint32_t msg_type,
588                                    const uint64_t msg_num) {
589     for (uint64_t i = 0; i < msg_num; ++i) {
590         if (!sendMessageFromAck(msg_type)) {
591             return (i);
592         }
593     }
594     return (msg_num);
595 }
596 
597 uint64_t
sendMultipleMessages6(const uint32_t msg_type,const uint64_t msg_num)598 TestControl::sendMultipleMessages6(const uint32_t msg_type,
599                                    const uint64_t msg_num) {
600     for (uint64_t i = 0; i < msg_num; ++i) {
601         if (!sendMessageFromReply(msg_type)) {
602             return (i);
603         }
604     }
605     return (msg_num);
606 }
607 
608 void
printDiagnostics() const609 TestControl::printDiagnostics() const {
610     if (options_.testDiags('a')) {
611         // Print all command line parameters.
612         options_.printCommandLine();
613         // Print MAC and DUID.
614         std::cout << "Set MAC to " << vector2Hex(options_.getMacTemplate(), "::")
615                   << std::endl;
616         if (options_.getDuidTemplate().size() > 0) {
617             std::cout << "Set DUID to " << vector2Hex(options_.getDuidTemplate()) << std::endl;
618         }
619     }
620 }
621 
622 void
printTemplate(const uint8_t packet_type) const623 TestControl::printTemplate(const uint8_t packet_type) const {
624     std::string hex_buf;
625     int arg_idx = 0;
626     if (options_.getIpVersion() == 4) {
627         if (packet_type == DHCPREQUEST) {
628             arg_idx = 1;
629         }
630         std::map<uint8_t, dhcp::Pkt4Ptr>::const_iterator pkt_it =
631             template_packets_v4_.find(packet_type);
632         if ((pkt_it != template_packets_v4_.end()) &&
633             pkt_it->second) {
634             const util::OutputBuffer& out_buf(pkt_it->second->getBuffer());
635             const char* out_buf_data =
636                 static_cast<const char*>(out_buf.getData());
637             std::vector<uint8_t> buf(out_buf_data, out_buf_data + out_buf.getLength());
638             hex_buf = vector2Hex(buf);
639         }
640     } else if (options_.getIpVersion() == 6) {
641         if (packet_type == DHCPV6_REQUEST) {
642             arg_idx = 1;
643         }
644         std::map<uint8_t, dhcp::Pkt6Ptr>::const_iterator pkt_it =
645             template_packets_v6_.find(packet_type);
646         if (pkt_it != template_packets_v6_.end() &&
647             pkt_it->second) {
648             const util::OutputBuffer& out_buf(pkt_it->second->getBuffer());
649             const char* out_buf_data =
650                 static_cast<const char*>(out_buf.getData());
651             std::vector<uint8_t> buf(out_buf_data, out_buf_data + out_buf.getLength());
652             hex_buf = vector2Hex(buf);
653         }
654     }
655     std::cout << "xid-offset=" << getTransactionIdOffset(arg_idx) << std::endl;
656     std::cout << "random-offset=" << getRandomOffset(arg_idx) << std::endl;
657     if (arg_idx > 0) {
658         std::cout << "srvid-offset=" << getServerIdOffset() << std::endl;
659         std::cout << "time-offset=" << getElapsedTimeOffset() << std::endl;
660         std::cout << "ip-offset=" << getRequestedIpOffset() << std::endl;
661     }
662 
663     std::cout << "contents: " << std::endl;
664     int line_len = 32;
665     int i = 0;
666     while (line_len == 32) {
667         if (hex_buf.length() - i < 32) {
668             line_len = hex_buf.length() - i;
669         };
670         if (line_len > 0) {
671             std::cout << setfill('0') << setw(4) << std::hex << i << std::dec
672                       << "   " << hex_buf.substr(i, line_len) << std::endl;
673         }
674         i += 32;
675     }
676     std::cout << std::endl;
677 }
678 
679 void
printTemplates() const680 TestControl::printTemplates() const {
681     if (options_.getIpVersion() == 4) {
682         printTemplate(DHCPDISCOVER);
683         printTemplate(DHCPREQUEST);
684     } else if (options_.getIpVersion() == 6) {
685         printTemplate(DHCPV6_SOLICIT);
686         printTemplate(DHCPV6_REQUEST);
687     }
688 }
689 
690 void
printRate() const691 TestControl::printRate() const {
692     double rate = 0;
693     std::string exchange_name = "4-way exchanges";
694     ExchangeType xchg_type = ExchangeType::DO;
695     if (options_.getIpVersion() == 4) {
696         xchg_type =
697             options_.getExchangeMode() == CommandOptions::DO_SA ?
698             ExchangeType::DO : ExchangeType::RA;
699         if (xchg_type == ExchangeType::DO) {
700             exchange_name = "DISCOVER-OFFER";
701         }
702     } else if (options_.getIpVersion() == 6) {
703         xchg_type =
704             options_.getExchangeMode() == CommandOptions::DO_SA ?
705             ExchangeType::SA : ExchangeType::RR;
706         if (xchg_type == ExchangeType::SA) {
707             exchange_name = options_.isRapidCommit() ? "Solicit-Reply" :
708                 "Solicit-Advertise";
709         }
710     }
711     double duration =
712         stats_mgr_.getTestPeriod().length().total_nanoseconds() / 1e9;
713     rate = stats_mgr_.getRcvdPacketsNum(xchg_type) / duration;
714     std::ostringstream s;
715     s << "***Rate statistics***" << std::endl;
716     s << "Rate: " << rate << " " << exchange_name << "/second";
717     if (options_.getRate() > 0) {
718         s << ", expected rate: " << options_.getRate() << std::endl;
719     }
720 
721     std::cout << s.str() << std::endl;
722 
723     std::cout <<"***Malformed Packets***" << std::endl
724               << "Malformed packets: " << ExchangeStats::malformed_pkts_
725 	      << std::endl;
726 }
727 
728 void
printIntermediateStats()729 TestControl::printIntermediateStats() {
730     int delay = options_.getReportDelay();
731     ptime now = microsec_clock::universal_time();
732     time_period time_since_report(last_report_, now);
733     if (time_since_report.length().total_seconds() >= delay) {
734         stats_mgr_.printIntermediateStats(options_.getCleanReport(),
735                                           options_.getCleanReportSeparator());
736         last_report_ = now;
737     }
738 }
739 
740 void
printStats() const741 TestControl::printStats() const {
742     printRate();
743     stats_mgr_.printStats();
744     if (options_.testDiags('i')) {
745         stats_mgr_.printCustomCounters();
746     }
747 }
748 
749 std::string
vector2Hex(const std::vector<uint8_t> & vec,const std::string & separator)750 TestControl::vector2Hex(const std::vector<uint8_t>& vec,
751                         const std::string& separator /* = "" */) {
752     std::ostringstream stream;
753     for (std::vector<uint8_t>::const_iterator it = vec.begin();
754          it != vec.end();
755          ++it) {
756         if (it == vec.begin()) {
757             stream << byte2Hex(*it);
758         } else {
759             stream << separator << byte2Hex(*it);
760         }
761     }
762     return (stream.str());
763 }
764 
765 void
readPacketTemplate(const std::string & file_name)766 TestControl::readPacketTemplate(const std::string& file_name) {
767     std::ifstream temp_file;
768     temp_file.open(file_name.c_str(), ios::in | ios::binary | ios::ate);
769     if (!temp_file.is_open()) {
770         isc_throw(BadValue, "unable to open template file " << file_name);
771     }
772     // Read template file contents.
773     std::streampos temp_size = temp_file.tellg();
774     if (temp_size == std::streampos(0)) {
775         temp_file.close();
776         isc_throw(OutOfRange, "the template file " << file_name << " is empty");
777     }
778     temp_file.seekg(0, ios::beg);
779     std::vector<char> file_contents(temp_size);
780     temp_file.read(&file_contents[0], temp_size);
781     temp_file.close();
782     // Spaces are allowed so we have to strip the contents
783     // from them. In the same time we want to make sure that
784     // apart from spaces the file contains hexadecimal digits
785     // only.
786     std::vector<char> hex_digits;
787     for (size_t i = 0; i < file_contents.size(); ++i) {
788         if (isxdigit(file_contents[i])) {
789             hex_digits.push_back(file_contents[i]);
790         } else if (!isxdigit(file_contents[i]) &&
791                    !isspace(file_contents[i])) {
792             isc_throw(BadValue, "'" << file_contents[i] << "' is not a"
793                       " hexadecimal digit");
794         }
795     }
796     // Expect even number of digits.
797     if (hex_digits.size() % 2 != 0) {
798         isc_throw(OutOfRange, "odd number of digits in template file");
799     } else if (hex_digits.empty()) {
800         isc_throw(OutOfRange, "template file " << file_name << " is empty");
801     }
802     std::vector<uint8_t> binary_stream;
803     for (size_t i = 0; i < hex_digits.size(); i += 2) {
804         stringstream s;
805         s << "0x" << hex_digits[i] << hex_digits[i+1];
806         int b;
807         s >> std::hex >> b;
808         binary_stream.push_back(static_cast<uint8_t>(b));
809     }
810     template_buffers_.push_back(binary_stream);
811 }
812 
813 void
processReceivedPacket4(const Pkt4Ptr & pkt4)814 TestControl::processReceivedPacket4(const Pkt4Ptr& pkt4) {
815     if (pkt4->getType() == DHCPOFFER) {
816         PktPtr pkt = stats_mgr_.passRcvdPacket(ExchangeType::DO, pkt4);
817         address4Uniqueness(pkt4, ExchangeType::DO);
818         Pkt4Ptr discover_pkt4(boost::dynamic_pointer_cast<Pkt4>(pkt));
819         CommandOptions::ExchangeMode xchg_mode = options_.getExchangeMode();
820         if ((xchg_mode == CommandOptions::DORA_SARR) && discover_pkt4) {
821             if (template_buffers_.size() < 2) {
822                 sendRequest4(discover_pkt4, pkt4);
823             } else {
824                 /// @todo add defines for packet type index that can be
825                 /// used to access template_buffers_.
826                 sendRequest4(template_buffers_[1], discover_pkt4, pkt4);
827             }
828         }
829     } else if (pkt4->getType() == DHCPACK) {
830         // If received message is DHCPACK, we have to check if this is
831         // a response to 4-way exchange. We'll match this packet with
832         // a DHCPREQUEST sent as part of the 4-way exchanges.
833         if (stats_mgr_.passRcvdPacket(ExchangeType::RA, pkt4)) {
834             address4Uniqueness(pkt4, ExchangeType::RA);
835             // The DHCPACK belongs to DHCPREQUEST-DHCPACK exchange type.
836             // So, we may need to keep this DHCPACK in the storage if renews.
837             // Note that, DHCPACK messages hold the information about
838             // leases assigned. We use this information to renew.
839             if (stats_mgr_.hasExchangeStats(ExchangeType::RNA) ||
840                 stats_mgr_.hasExchangeStats(ExchangeType::RLA)) {
841                 // Renew or release messages are sent, because StatsMgr has the
842                 // specific exchange type specified. Let's append the DHCPACK
843                 // message to a storage.
844                 ack_storage_.append(pkt4);
845             }
846         // The DHCPACK message is not a server's response to the DHCPREQUEST
847         // message sent within the 4-way exchange. It may be a response to a
848         // renewal. In this case we first check if StatsMgr has exchange type
849         // for renew specified, and if it has, if there is a corresponding
850         // renew message for the received DHCPACK.
851         } else if (stats_mgr_.hasExchangeStats(ExchangeType::RNA)) {
852             stats_mgr_.passRcvdPacket(ExchangeType::RNA, pkt4);
853         }
854     }
855 }
856 
857 void
address6Uniqueness(const Pkt6Ptr & pkt6,ExchangeType xchg_type)858 TestControl::address6Uniqueness(const Pkt6Ptr& pkt6, ExchangeType xchg_type) {
859     // check if received address is unique
860     if (options_.getAddrUnique()) {
861         std::set<std::string> current;
862         // addresses were already checked in validateIA
863         // we can safely assume that those are correct
864         for (OptionCollection::iterator opt = pkt6->options_.begin();
865              opt != pkt6->options_.end(); ++opt) {
866             switch (opt->second->getType()) {
867             case D6O_IA_PD: {
868                 // add address and check if it has not been already assigned
869                 // addresses should be unique cross options of the packet
870                 auto ret = current.emplace(boost::dynamic_pointer_cast<
871                     Option6IAPrefix>(opt->second->getOption(D6O_IAPREFIX))->getAddress().toText());
872                 if (!ret.second) {
873                     stats_mgr_.updateNonUniqueAddrNum(xchg_type);
874                 }
875                 break;
876 	    }
877 	    case D6O_IA_NA: {
878                 // add address and check if it has not been already assigned
879                 // addresses should be unique cross options of the packet
880                 auto ret = current.emplace(boost::dynamic_pointer_cast<
881                     Option6IAAddr>(opt->second->getOption(D6O_IAADDR))->getAddress().toText());
882                 if (!ret.second) {
883                     stats_mgr_.updateNonUniqueAddrNum(xchg_type);
884                 }
885                 break;
886             }
887             default:
888                 break;
889             }
890         }
891         // addresses should be unique cross packets
892         addUniqeAddr(current, xchg_type);
893     }
894 }
895 
896 void
address4Uniqueness(const Pkt4Ptr & pkt4,ExchangeType xchg_type)897 TestControl::address4Uniqueness(const Pkt4Ptr& pkt4, ExchangeType xchg_type) {
898     // check if received address is unique
899     if (options_.getAddrUnique()) {
900         // addresses were already checked in validateIA
901         // we can safely assume that those are correct
902         std::set<std::string> current;
903         current.insert(pkt4->getYiaddr().toText());
904         // addresses should be unique cross packets
905         addUniqeAddr(current, xchg_type);
906     }
907 }
908 
909 bool
validateIA(const Pkt6Ptr & pkt6)910 TestControl::validateIA(const Pkt6Ptr& pkt6) {
911     // check if iaaddr exists - if it does, we can continue sending request
912     // if not we will update statistics about rejected leases
913     // @todo it's checking just one iaaddress option for now it's ok
914     // but when perfdhcp will be extended to create message with multiple IA
915     // this will have to be iterate on:
916     // OptionCollection ias = pkt6->getOptions(D6O_IA_NA);
917     Option6IAPrefixPtr iapref;
918     Option6IAAddrPtr iaaddr;
919     if (pkt6->getOption(D6O_IA_PD)) {
920         iapref = boost::dynamic_pointer_cast<
921                  Option6IAPrefix>(pkt6->getOption(D6O_IA_PD)->getOption(D6O_IAPREFIX));
922     }
923     if (pkt6->getOption(D6O_IA_NA)) {
924         iaaddr = boost::dynamic_pointer_cast<
925                  Option6IAAddr>(pkt6->getOption(D6O_IA_NA)->getOption(D6O_IAADDR));
926     }
927 
928     bool address_and_prefix = options_.getLeaseType().includes(
929                               CommandOptions::LeaseType::ADDRESS_AND_PREFIX);
930     bool prefix_only = options_.getLeaseType().includes(
931                        CommandOptions::LeaseType::PREFIX);
932     bool address_only = options_.getLeaseType().includes(
933                         CommandOptions::LeaseType::ADDRESS);
934     if ((address_and_prefix && iapref && iaaddr) ||
935         (prefix_only && iapref && !address_and_prefix) ||
936         (address_only && iaaddr && !address_and_prefix)) {
937         return true;
938     } else {
939         return false;
940     }
941 }
942 
943 void
processReceivedPacket6(const Pkt6Ptr & pkt6)944 TestControl::processReceivedPacket6(const Pkt6Ptr& pkt6) {
945     uint8_t packet_type = pkt6->getType();
946     if (packet_type == DHCPV6_ADVERTISE) {
947         PktPtr pkt = stats_mgr_.passRcvdPacket(ExchangeType::SA, pkt6);
948         Pkt6Ptr solicit_pkt6(boost::dynamic_pointer_cast<Pkt6>(pkt));
949         CommandOptions::ExchangeMode xchg_mode = options_.getExchangeMode();
950         if ((xchg_mode == CommandOptions::DORA_SARR) && solicit_pkt6) {
951             if (validateIA(pkt6)) {
952                // if address is correct - check uniqueness
953                address6Uniqueness(pkt6, ExchangeType::SA);
954                if (template_buffers_.size() < 2) {
955                     sendRequest6(pkt6);
956                } else {
957                     /// @todo add defines for packet type index that can be
958                     /// used to access template_buffers_.
959                     sendRequest6(template_buffers_[1], pkt6);
960                }
961             } else {
962                 stats_mgr_.updateRejLeases(ExchangeType::SA);
963             }
964         }
965     } else if (packet_type == DHCPV6_REPLY) {
966         // If the received message is Reply, we have to find out which exchange
967         // type the Reply message belongs to. It is doable by matching the Reply
968         // transaction id with the transaction id of the sent Request, Renew
969         // or Release. First we start with the Request.
970         if (stats_mgr_.passRcvdPacket(ExchangeType::RR, pkt6)) {
971             // The Reply belongs to Request-Reply exchange type. So, we may need
972             // to keep this Reply in the storage if Renews or/and Releases are
973             // being sent. Note that, Reply messages hold the information about
974             // leases assigned. We use this information to construct Renew and
975             // Release messages.
976             if (validateIA(pkt6)) {
977                 // if address is correct - check uniqueness
978                 address6Uniqueness(pkt6, ExchangeType::RR);
979                 // check if there is correct IA to continue with Renew/Release
980                 if (stats_mgr_.hasExchangeStats(ExchangeType::RN) ||
981                     stats_mgr_.hasExchangeStats(ExchangeType::RL)) {
982                     // Renew or Release messages are sent, because StatsMgr has the
983                     // specific exchange type specified. Let's append the Reply
984                     // message to a storage.
985                     reply_storage_.append(pkt6);
986                 }
987             } else {
988                 stats_mgr_.updateRejLeases(ExchangeType::RR);
989             }
990         // The Reply message is not a server's response to the Request message
991         // sent within the 4-way exchange. It may be a response to the Renew
992         // or Release message. In the if clause we first check if StatsMgr
993         // has exchange type for Renew specified, and if it has, if there is
994         // a corresponding Renew message for the received Reply. If not,
995         // we check that StatsMgr has exchange type for Release specified,
996         // as possibly the Reply has been sent in response to Release.
997         } else if (!(stats_mgr_.hasExchangeStats(ExchangeType::RN) &&
998                      stats_mgr_.passRcvdPacket(ExchangeType::RN, pkt6)) &&
999                    stats_mgr_.hasExchangeStats(ExchangeType::RL)) {
1000             // At this point, it is only possible that the Reply has been sent
1001             // in response to a Release. Try to match the Reply with Release.
1002             stats_mgr_.passRcvdPacket(ExchangeType::RL, pkt6);
1003         }
1004     }
1005 }
1006 
1007 unsigned int
consumeReceivedPackets()1008 TestControl::consumeReceivedPackets() {
1009     unsigned int pkt_count = 0;
1010     PktPtr pkt;
1011     while ((pkt = receiver_.getPkt())) {
1012         pkt_count += 1;
1013         if (options_.getIpVersion() == 4) {
1014             Pkt4Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4>(pkt);
1015             processReceivedPacket4(pkt4);
1016         } else {
1017             Pkt6Ptr pkt6 = boost::dynamic_pointer_cast<Pkt6>(pkt);
1018             processReceivedPacket6(pkt6);
1019         }
1020     }
1021     return pkt_count;
1022 }
1023 void
registerOptionFactories4() const1024 TestControl::registerOptionFactories4() const {
1025     static bool factories_registered = false;
1026     if (!factories_registered) {
1027         // DHCP_MESSAGE_TYPE option factory.
1028         LibDHCP::OptionFactoryRegister(Option::V4,
1029                                        DHO_DHCP_MESSAGE_TYPE,
1030                                        &TestControl::factoryGeneric);
1031         // DHCP_SERVER_IDENTIFIER option factory.
1032         LibDHCP::OptionFactoryRegister(Option::V4,
1033                                        DHO_DHCP_SERVER_IDENTIFIER,
1034                                        &TestControl::factoryGeneric);
1035         // DHCP_PARAMETER_REQUEST_LIST option factory.
1036         LibDHCP::OptionFactoryRegister(Option::V4,
1037                                        DHO_DHCP_PARAMETER_REQUEST_LIST,
1038                                        &TestControl::factoryRequestList4);
1039     }
1040     factories_registered = true;
1041 }
1042 
1043 void
registerOptionFactories6() const1044 TestControl::registerOptionFactories6() const {
1045     static bool factories_registered = false;
1046     if (!factories_registered) {
1047         // D6O_ELAPSED_TIME
1048         LibDHCP::OptionFactoryRegister(Option::V6,
1049                                        D6O_ELAPSED_TIME,
1050                                        &TestControl::factoryElapsedTime6);
1051         // D6O_RAPID_COMMIT
1052         LibDHCP::OptionFactoryRegister(Option::V6,
1053                                        D6O_RAPID_COMMIT,
1054                                        &TestControl::factoryRapidCommit6);
1055         // D6O_ORO (option request option) factory.
1056         LibDHCP::OptionFactoryRegister(Option::V6,
1057                                        D6O_ORO,
1058                                        &TestControl::factoryOptionRequestOption6);
1059         // D6O_CLIENTID option factory.
1060         LibDHCP::OptionFactoryRegister(Option::V6,
1061                                        D6O_CLIENTID,
1062                                        &TestControl::factoryGeneric);
1063         // D6O_SERVERID option factory.
1064         LibDHCP::OptionFactoryRegister(Option::V6,
1065                                        D6O_SERVERID,
1066                                        &TestControl::factoryGeneric);
1067         // D6O_IA_NA option factory.
1068         LibDHCP::OptionFactoryRegister(Option::V6,
1069                                        D6O_IA_NA,
1070                                        &TestControl::factoryIana6);
1071 
1072         // D6O_IA_PD option factory.
1073         LibDHCP::OptionFactoryRegister(Option::V6,
1074                                        D6O_IA_PD,
1075                                        &TestControl::factoryIapd6);
1076 
1077 
1078     }
1079     factories_registered = true;
1080 }
1081 
1082 void
registerOptionFactories() const1083 TestControl::registerOptionFactories() const {
1084     switch(options_.getIpVersion()) {
1085     case 4:
1086         registerOptionFactories4();
1087         break;
1088     case 6:
1089         registerOptionFactories6();
1090         break;
1091     default:
1092         isc_throw(InvalidOperation, "command line options have to be parsed "
1093                   "before DHCP option factories can be registered");
1094     }
1095 }
1096 
1097 void
reset()1098 TestControl::reset() {
1099     transid_gen_.reset();
1100     last_report_ = microsec_clock::universal_time();
1101     // Actual generators will have to be set later on because we need to
1102     // get command line parameters first.
1103     setTransidGenerator(NumberGeneratorPtr());
1104     setMacAddrGenerator(NumberGeneratorPtr());
1105     first_packet_serverid_.clear();
1106     interrupted_ = false;
1107 }
1108 
TestControl(CommandOptions & options,BasePerfSocket & socket)1109 TestControl::TestControl(CommandOptions& options, BasePerfSocket &socket) :
1110     exit_time_(not_a_date_time),
1111     number_generator_(0, options.getMacsFromFile().size()),
1112     socket_(socket),
1113     receiver_(socket, options.isSingleThreaded(), options.getIpVersion()),
1114     stats_mgr_(options),
1115     options_(options)
1116 {
1117     // Reset singleton state before test starts.
1118     reset();
1119 
1120     // Ip version is not set ONLY in case the command options
1121     // were not parsed. This surely means that parse() function
1122     // was not called prior to starting the test. This is fatal
1123     // error.
1124     if (options_.getIpVersion() == 0) {
1125         isc_throw(InvalidOperation,
1126                   "command options must be parsed before running a test");
1127     } else if (options_.getIpVersion() == 4) {
1128         // Turn off packet queueing.
1129         IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, data::ElementPtr());
1130         setTransidGenerator(NumberGeneratorPtr(new SequentialGenerator()));
1131     } else {
1132         // Turn off packet queueing.
1133         IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, data::ElementPtr());
1134         setTransidGenerator(NumberGeneratorPtr(new SequentialGenerator(0x00FFFFFF)));
1135     }
1136 
1137     uint32_t clients_num = options_.getClientsNum() == 0 ?
1138         1 : options_.getClientsNum();
1139     setMacAddrGenerator(NumberGeneratorPtr(new SequentialGenerator(clients_num)));
1140 
1141     // Diagnostics are command line options mainly.
1142     printDiagnostics();
1143     // Option factories have to be registered.
1144     registerOptionFactories();
1145     // Initialize packet templates.
1146     initPacketTemplates();
1147     // Initialize randomization seed.
1148     if (options_.isSeeded()) {
1149         srandom(options_.getSeed());
1150     } else {
1151         // Seed with current time.
1152         time_period duration(from_iso_string("20111231T235959"),
1153                              microsec_clock::universal_time());
1154         srandom(duration.length().total_seconds()
1155                 + duration.length().fractional_seconds());
1156     }
1157     // If user interrupts the program we will exit gracefully.
1158     signal(SIGINT, TestControl::handleInterrupt);
1159 }
1160 
1161 void
runWrapped(bool do_stop) const1162 TestControl::runWrapped(bool do_stop /*= false */) const {
1163     if (!options_.getWrapped().empty()) {
1164         pid_t pid = 0;
1165         signal(SIGCHLD, handleChild);
1166         pid = fork();
1167         if (pid < 0) {
1168             isc_throw(Unexpected, "unable to fork");
1169         } else if (pid == 0) {
1170             execlp(options_.getWrapped().c_str(), do_stop ? "stop" : "start", (void*)0);
1171         }
1172     }
1173 }
1174 
1175 void
saveFirstPacket(const Pkt4Ptr & pkt)1176 TestControl::saveFirstPacket(const Pkt4Ptr& pkt) {
1177     if (options_.testDiags('T')) {
1178         if (template_packets_v4_.find(pkt->getType()) == template_packets_v4_.end()) {
1179             template_packets_v4_[pkt->getType()] = pkt;
1180         }
1181     }
1182 }
1183 
1184 void
saveFirstPacket(const Pkt6Ptr & pkt)1185 TestControl::saveFirstPacket(const Pkt6Ptr& pkt) {
1186     if (options_.testDiags('T')) {
1187         if (template_packets_v6_.find(pkt->getType()) == template_packets_v6_.end()) {
1188             template_packets_v6_[pkt->getType()] = pkt;
1189         }
1190     }
1191 }
1192 
1193 void
sendDiscover4(const bool preload)1194 TestControl::sendDiscover4(const bool preload /*= false*/) {
1195     // Generate the MAC address to be passed in the packet.
1196     uint8_t randomized = 0;
1197     std::vector<uint8_t> mac_address = generateMacAddress(randomized);
1198     // Generate transaction id to be set for the new exchange.
1199     const uint32_t transid = generateTransid();
1200     Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, transid));
1201     if (!pkt4) {
1202         isc_throw(Unexpected, "failed to create DISCOVER packet");
1203     }
1204 
1205     // Delete the default Message Type option set by Pkt4
1206     pkt4->delOption(DHO_DHCP_MESSAGE_TYPE);
1207 
1208     // Set options: DHCP_MESSAGE_TYPE and DHCP_PARAMETER_REQUEST_LIST
1209     OptionBuffer buf_msg_type;
1210     buf_msg_type.push_back(DHCPDISCOVER);
1211     pkt4->addOption(Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE,
1212                                     buf_msg_type));
1213     pkt4->addOption(Option::factory(Option::V4,
1214                                     DHO_DHCP_PARAMETER_REQUEST_LIST));
1215 
1216 
1217     // Set client's and server's ports as well as server's address,
1218     // and local (relay) address.
1219     setDefaults4(pkt4);
1220 
1221     // Set hardware address
1222     pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address);
1223 
1224     // Set client identifier
1225     pkt4->addOption(generateClientId(pkt4->getHWAddr()));
1226 
1227     // Check if we need to simulate HA failures by pretending no responses were received.
1228     // The DHCP protocol signals that by increasing secs field (seconds since the configuration attempt started).
1229     if (options_.getIncreaseElapsedTime() &&
1230         stats_mgr_.getTestPeriod().length().total_seconds() >= options_.getWaitForElapsedTime() &&
1231         stats_mgr_.getTestPeriod().length().total_seconds() < options_.getWaitForElapsedTime() +
1232                                      options_.getIncreaseElapsedTime()) {
1233 
1234         // Keep increasing elapsed time. The value should start increasing steadily.
1235         uint32_t val = stats_mgr_.getTestPeriod().length().total_seconds() - options_.getWaitForElapsedTime() + 1;
1236         if (val > 65535) {
1237             val = 65535;
1238         }
1239         pkt4->setSecs(static_cast<uint16_t>(val));
1240     }
1241 
1242     // Add any extra options that user may have specified.
1243     addExtraOpts(pkt4);
1244 
1245     pkt4->pack();
1246     socket_.send(pkt4);
1247     if (!preload) {
1248         stats_mgr_.passSentPacket(ExchangeType::DO, pkt4);
1249     }
1250 
1251     saveFirstPacket(pkt4);
1252 }
1253 
1254 void
sendDiscover4(const std::vector<uint8_t> & template_buf,const bool preload)1255 TestControl::sendDiscover4(const std::vector<uint8_t>& template_buf,
1256                            const bool preload /* = false */) {
1257     // Get the first argument if multiple the same arguments specified
1258     // in the command line. First one refers to DISCOVER packets.
1259     const uint8_t arg_idx = 0;
1260     // Generate the MAC address to be passed in the packet.
1261     uint8_t randomized = 0;
1262     std::vector<uint8_t> mac_address = generateMacAddress(randomized);
1263     // Generate transaction id to be set for the new exchange.
1264     const uint32_t transid = generateTransid();
1265     // Get transaction id offset.
1266     size_t transid_offset = getTransactionIdOffset(arg_idx);
1267     // Get randomization offset.
1268     // We need to go back by HW_ETHER_LEN (MAC address length)
1269     // because this offset points to last octet of MAC address.
1270     size_t rand_offset = getRandomOffset(arg_idx) - HW_ETHER_LEN + 1;
1271     // Create temporary buffer with template contents. We will
1272     // modify this temporary buffer but we don't want to modify
1273     // the original template.
1274     std::vector<uint8_t> in_buf(template_buf.begin(),
1275                                 template_buf.end());
1276     // Check if we are not going out of bounds.
1277     if (rand_offset + HW_ETHER_LEN > in_buf.size()) {
1278         isc_throw(OutOfRange, "randomization offset is out of bounds");
1279     }
1280     PerfPkt4Ptr pkt4(new PerfPkt4(&in_buf[0], in_buf.size(),
1281                                   transid_offset,
1282                                   transid));
1283 
1284     // Replace MAC address in the template with actual MAC address.
1285     pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end());
1286     // Create a packet from the temporary buffer.
1287     setDefaults4(boost::static_pointer_cast<Pkt4>(pkt4));
1288     // Pack the input packet buffer to output buffer so as it can
1289     // be sent to server.
1290     pkt4->rawPack();
1291     socket_.send(boost::static_pointer_cast<Pkt4>(pkt4));
1292     if (!preload) {
1293         // Update packet stats.
1294         stats_mgr_.passSentPacket(ExchangeType::DO,
1295                                     boost::static_pointer_cast<Pkt4>(pkt4));
1296     }
1297     saveFirstPacket(pkt4);
1298 }
1299 
1300 bool
sendMessageFromAck(const uint16_t msg_type)1301 TestControl::sendMessageFromAck(const uint16_t msg_type) {
1302     // We only permit Request or Release messages to be sent using this
1303     // function.
1304     if (msg_type != DHCPREQUEST && msg_type != DHCPRELEASE) {
1305         isc_throw(isc::BadValue,
1306                   "invalid message type "
1307                       << msg_type
1308                       << " to be sent, expected DHCPREQUEST or DHCPRELEASE");
1309     }
1310 
1311     // Get one of the recorded DHCPACK messages.
1312     Pkt4Ptr ack = ack_storage_.getRandom();
1313     if (!ack) {
1314         return (false);
1315     }
1316 
1317     // Create message of the specified type.
1318     Pkt4Ptr msg = createMessageFromAck(msg_type, ack);
1319     setDefaults4(msg);
1320 
1321     // Override relay address
1322     msg->setGiaddr(ack->getGiaddr());
1323 
1324     // Add any extra options that user may have specified.
1325     addExtraOpts(msg);
1326 
1327     // Pack it.
1328     msg->pack();
1329 
1330     // And send it.
1331     socket_.send(msg);
1332     address4Uniqueness(msg, ExchangeType::RLA);
1333     stats_mgr_.passSentPacket((msg_type == DHCPREQUEST ? ExchangeType::RNA :
1334                                                          ExchangeType::RLA),
1335                               msg);
1336     return (true);
1337 }
1338 
1339 
1340 bool
sendMessageFromReply(const uint16_t msg_type)1341 TestControl::sendMessageFromReply(const uint16_t msg_type) {
1342     // We only permit Release or Renew messages to be sent using this function.
1343     if (msg_type != DHCPV6_RENEW && msg_type != DHCPV6_RELEASE) {
1344         isc_throw(isc::BadValue, "invalid message type " << msg_type
1345                   << " to be sent, expected DHCPV6_RENEW or DHCPV6_RELEASE");
1346     }
1347 
1348     // Get one of the recorded DHCPV6_OFFER messages.
1349     Pkt6Ptr reply = reply_storage_.getRandom();
1350     if (!reply) {
1351         return (false);
1352     }
1353     // Prepare the message of the specified type.
1354     Pkt6Ptr msg = createMessageFromReply(msg_type, reply);
1355     setDefaults6(msg);
1356 
1357     // Add any extra options that user may have specified.
1358     addExtraOpts(msg);
1359 
1360     // Pack it.
1361     msg->pack();
1362 
1363     // And send it.
1364     socket_.send(msg);
1365     address6Uniqueness(msg, ExchangeType::RL);
1366     stats_mgr_.passSentPacket((msg_type == DHCPV6_RENEW ? ExchangeType::RN
1367                                 : ExchangeType::RL), msg);
1368     return (true);
1369 }
1370 
1371 void
sendRequest4(const dhcp::Pkt4Ptr & discover_pkt4,const dhcp::Pkt4Ptr & offer_pkt4)1372 TestControl::sendRequest4(const dhcp::Pkt4Ptr& discover_pkt4,
1373                           const dhcp::Pkt4Ptr& offer_pkt4) {
1374     // Use the same transaction id as the one used in the discovery packet.
1375     const uint32_t transid = discover_pkt4->getTransid();
1376     Pkt4Ptr pkt4(new Pkt4(DHCPREQUEST, transid));
1377 
1378     // Use first flags indicates that we want to use the server
1379     // id captured in first packet.
1380     if (options_.isUseFirst() &&
1381         (first_packet_serverid_.size() > 0)) {
1382         pkt4->addOption(Option::factory(Option::V4, DHO_DHCP_SERVER_IDENTIFIER,
1383                                         first_packet_serverid_));
1384     } else {
1385         OptionPtr opt_serverid =
1386             offer_pkt4->getOption(DHO_DHCP_SERVER_IDENTIFIER);
1387         if (!opt_serverid) {
1388             isc_throw(BadValue, "there is no SERVER_IDENTIFIER option "
1389                       << "in OFFER message");
1390         }
1391         if (stats_mgr_.getRcvdPacketsNum(ExchangeType::DO) == 1) {
1392             first_packet_serverid_ = opt_serverid->getData();
1393         }
1394         pkt4->addOption(opt_serverid);
1395     }
1396 
1397     /// Set client address.
1398     asiolink::IOAddress yiaddr = offer_pkt4->getYiaddr();
1399     if (!yiaddr.isV4()) {
1400         isc_throw(BadValue, "the YIADDR returned in OFFER packet is not "
1401                   " IPv4 address");
1402     }
1403     OptionPtr opt_requested_address =
1404         OptionPtr(new Option(Option::V4, DHO_DHCP_REQUESTED_ADDRESS,
1405                              OptionBuffer()));
1406     opt_requested_address->setUint32(yiaddr.toUint32());
1407     pkt4->addOption(opt_requested_address);
1408     OptionPtr opt_parameter_list =
1409         Option::factory(Option::V4, DHO_DHCP_PARAMETER_REQUEST_LIST);
1410     pkt4->addOption(opt_parameter_list);
1411     // Set client's and server's ports as well as server's address
1412     setDefaults4(pkt4);
1413     // Override relay address
1414     pkt4->setGiaddr(offer_pkt4->getGiaddr());
1415     // Add any extra options that user may have specified.
1416     addExtraOpts(pkt4);
1417 
1418     // Set hardware address
1419     pkt4->setHWAddr(offer_pkt4->getHWAddr());
1420     // Set client id.
1421     pkt4->addOption(generateClientId(pkt4->getHWAddr()));
1422     // Set elapsed time.
1423     uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4);
1424     pkt4->setSecs(static_cast<uint16_t>(elapsed_time / 1000));
1425     // Prepare on wire data to send.
1426     pkt4->pack();
1427     socket_.send(pkt4);
1428     stats_mgr_.passSentPacket(ExchangeType::RA, pkt4);
1429     saveFirstPacket(pkt4);
1430 }
1431 
1432 void
sendRequest4(const std::vector<uint8_t> & template_buf,const dhcp::Pkt4Ptr & discover_pkt4,const dhcp::Pkt4Ptr & offer_pkt4)1433 TestControl::sendRequest4(const std::vector<uint8_t>& template_buf,
1434                           const dhcp::Pkt4Ptr& discover_pkt4,
1435                           const dhcp::Pkt4Ptr& offer_pkt4) {
1436     // Get the second argument if multiple the same arguments specified
1437     // in the command line. Second one refers to REQUEST packets.
1438     const uint8_t arg_idx = 1;
1439     // Use the same transaction id as the one used in the discovery packet.
1440     const uint32_t transid = discover_pkt4->getTransid();
1441     // Get transaction id offset.
1442     size_t transid_offset = getTransactionIdOffset(arg_idx);
1443     // Get the offset of MAC's last octet.
1444     // We need to go back by HW_ETHER_LEN (MAC address length)
1445     // because this offset points to last octet of MAC address.
1446     size_t rand_offset = getRandomOffset(arg_idx) - HW_ETHER_LEN + 1;
1447     // Create temporary buffer from the template.
1448     std::vector<uint8_t> in_buf(template_buf.begin(),
1449                                 template_buf.end());
1450     // Check if given randomization offset is not out of bounds.
1451     if (rand_offset + HW_ETHER_LEN > in_buf.size()) {
1452         isc_throw(OutOfRange, "randomization offset is out of bounds");
1453     }
1454 
1455     // Create packet from the temporary buffer.
1456     PerfPkt4Ptr pkt4(new PerfPkt4(&in_buf[0], in_buf.size(),
1457                                   transid_offset,
1458                                   transid));
1459 
1460      // Set hardware address from OFFER packet received.
1461     HWAddrPtr hwaddr = offer_pkt4->getHWAddr();
1462     std::vector<uint8_t> mac_address(HW_ETHER_LEN, 0);
1463     uint8_t hw_len = hwaddr->hwaddr_.size();
1464     if (hw_len != 0) {
1465         memcpy(&mac_address[0], &hwaddr->hwaddr_[0],
1466                hw_len > HW_ETHER_LEN ? HW_ETHER_LEN : hw_len);
1467     }
1468     pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end());
1469 
1470     // Set elapsed time.
1471     size_t elp_offset = getElapsedTimeOffset();
1472     uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4);
1473     pkt4->writeValueAt<uint16_t>(elp_offset,
1474                                  static_cast<uint16_t>(elapsed_time / 1000));
1475 
1476     // Get the actual server id offset.
1477     size_t sid_offset = getServerIdOffset();
1478     // Use first flags indicates that we want to use the server
1479     // id captured in first packet.
1480     if (options_.isUseFirst() &&
1481         (first_packet_serverid_.size() > 0)) {
1482         boost::shared_ptr<LocalizedOption>
1483             opt_serverid(new LocalizedOption(Option::V4,
1484                                              DHO_DHCP_SERVER_IDENTIFIER,
1485                                              first_packet_serverid_,
1486                                              sid_offset));
1487         pkt4->addOption(opt_serverid);
1488     } else {
1489         // Copy the contents of server identifier received in
1490         // OFFER packet to put this into REQUEST.
1491         OptionPtr opt_serverid_offer =
1492             offer_pkt4->getOption(DHO_DHCP_SERVER_IDENTIFIER);
1493         if (!opt_serverid_offer) {
1494             isc_throw(BadValue, "there is no SERVER_IDENTIFIER option "
1495                       << "in OFFER message");
1496         }
1497         boost::shared_ptr<LocalizedOption>
1498             opt_serverid(new LocalizedOption(Option::V4,
1499                                              DHO_DHCP_SERVER_IDENTIFIER,
1500                                              opt_serverid_offer->getData(),
1501                                              sid_offset));
1502         pkt4->addOption(opt_serverid);
1503         if (stats_mgr_.getRcvdPacketsNum(ExchangeType::DO) == 1) {
1504             first_packet_serverid_ = opt_serverid_offer->getData();
1505         }
1506     }
1507 
1508     /// Set client address.
1509     asiolink::IOAddress yiaddr = offer_pkt4->getYiaddr();
1510     if (!yiaddr.isV4()) {
1511         isc_throw(BadValue, "the YIADDR returned in OFFER packet is not "
1512                   " IPv4 address");
1513     }
1514 
1515     // Get the actual offset of requested ip.
1516     size_t rip_offset = getRequestedIpOffset();
1517     // Place requested IP option at specified position (rip_offset).
1518     boost::shared_ptr<LocalizedOption>
1519         opt_requested_ip(new LocalizedOption(Option::V4,
1520                                              DHO_DHCP_REQUESTED_ADDRESS,
1521                                              OptionBuffer(),
1522                                              rip_offset));
1523     // The IOAddress is convertible to uint32_t and returns exactly what we need.
1524     opt_requested_ip->setUint32(yiaddr.toUint32());
1525     pkt4->addOption(opt_requested_ip);
1526 
1527     setDefaults4(boost::static_pointer_cast<Pkt4>(pkt4));
1528 
1529     // Add any extra options that user may have specified.
1530     addExtraOpts(pkt4);
1531 
1532     // Prepare on-wire data.
1533     pkt4->rawPack();
1534     socket_.send(boost::static_pointer_cast<Pkt4>(pkt4));
1535     // Update packet stats.
1536     stats_mgr_.passSentPacket(ExchangeType::RA,
1537                                boost::static_pointer_cast<Pkt4>(pkt4));
1538     saveFirstPacket(pkt4);
1539 }
1540 
1541 void
sendRequest6(const Pkt6Ptr & advertise_pkt6)1542 TestControl::sendRequest6(const Pkt6Ptr& advertise_pkt6) {
1543     const uint32_t transid = generateTransid();
1544     Pkt6Ptr pkt6(new Pkt6(DHCPV6_REQUEST, transid));
1545     // Set elapsed time.
1546     OptionPtr opt_elapsed_time =
1547         Option::factory(Option::V6, D6O_ELAPSED_TIME);
1548     pkt6->addOption(opt_elapsed_time);
1549     // Set client id.
1550     OptionPtr opt_clientid = advertise_pkt6->getOption(D6O_CLIENTID);
1551     if (!opt_clientid) {
1552         isc_throw(Unexpected, "client id not found in received packet");
1553     }
1554     pkt6->addOption(opt_clientid);
1555 
1556     // Use first flags indicates that we want to use the server
1557     // id captured in first packet.
1558     if (options_.isUseFirst() &&
1559         (first_packet_serverid_.size() > 0)) {
1560         pkt6->addOption(Option::factory(Option::V6, D6O_SERVERID,
1561                                         first_packet_serverid_));
1562     } else {
1563         OptionPtr opt_serverid = advertise_pkt6->getOption(D6O_SERVERID);
1564         if (!opt_serverid) {
1565             isc_throw(Unexpected, "server id not found in received packet");
1566         }
1567         if (stats_mgr_.getRcvdPacketsNum(ExchangeType::SA) == 1) {
1568             first_packet_serverid_ = opt_serverid->getData();
1569         }
1570         pkt6->addOption(opt_serverid);
1571     }
1572 
1573     // Copy IA_NA or IA_PD option from the Advertise message to the Request
1574     // message being sent to the server. This will throw exception if the
1575     // option to be copied is not found. Note that this function will copy
1576     // one of IA_NA or IA_PD options, depending on the lease-type value
1577     // specified in the command line.
1578     copyIaOptions(advertise_pkt6, pkt6);
1579 
1580     // Set default packet data.
1581     setDefaults6(pkt6);
1582 
1583     // Add any extra options that user may have specified.
1584     addExtraOpts(pkt6);
1585 
1586     // Prepare on-wire data.
1587     pkt6->pack();
1588     socket_.send(pkt6);
1589     stats_mgr_.passSentPacket(ExchangeType::RR, pkt6);
1590     saveFirstPacket(pkt6);
1591 }
1592 
1593 void
sendRequest6(const std::vector<uint8_t> & template_buf,const Pkt6Ptr & advertise_pkt6)1594 TestControl::sendRequest6(const std::vector<uint8_t>& template_buf,
1595                           const Pkt6Ptr& advertise_pkt6) {
1596     // Get the second argument if multiple the same arguments specified
1597     // in the command line. Second one refers to REQUEST packets.
1598     const uint8_t arg_idx = 1;
1599     // Generate transaction id.
1600     const uint32_t transid = generateTransid();
1601     // Get transaction id offset.
1602     size_t transid_offset = getTransactionIdOffset(arg_idx);
1603     PerfPkt6Ptr pkt6(new PerfPkt6(&template_buf[0], template_buf.size(),
1604                                   transid_offset, transid));
1605     // Set elapsed time.
1606     size_t elp_offset = getElapsedTimeOffset();
1607     boost::shared_ptr<LocalizedOption>
1608         opt_elapsed_time(new LocalizedOption(Option::V6, D6O_ELAPSED_TIME,
1609                                              OptionBuffer(), elp_offset));
1610     pkt6->addOption(opt_elapsed_time);
1611 
1612     // Get the actual server id offset.
1613     size_t sid_offset = getServerIdOffset();
1614     // Use first flags indicates that we want to use the server
1615     // id captured in first packet.
1616     if (options_.isUseFirst() &&
1617         (first_packet_serverid_.size() > 0)) {
1618         boost::shared_ptr<LocalizedOption>
1619             opt_serverid(new LocalizedOption(Option::V6,
1620                                              D6O_SERVERID,
1621                                              first_packet_serverid_,
1622                                              sid_offset));
1623         pkt6->addOption(opt_serverid);
1624 
1625     } else {
1626         // Copy the contents of server identifier received in
1627         // ADVERTISE packet to put this into REQUEST.
1628         OptionPtr opt_serverid_advertise =
1629             advertise_pkt6->getOption(D6O_SERVERID);
1630         if (!opt_serverid_advertise) {
1631             isc_throw(BadValue, "there is no SERVERID option "
1632                       << "in ADVERTISE message");
1633         }
1634         boost::shared_ptr<LocalizedOption>
1635             opt_serverid(new LocalizedOption(Option::V6,
1636                                              D6O_SERVERID,
1637                                              opt_serverid_advertise->getData(),
1638                                              sid_offset));
1639         pkt6->addOption(opt_serverid);
1640         if (stats_mgr_.getRcvdPacketsNum(ExchangeType::SA) == 1) {
1641             first_packet_serverid_ = opt_serverid_advertise->getData();
1642         }
1643     }
1644     // Set IA_NA
1645     boost::shared_ptr<Option6IA> opt_ia_na_advertise =
1646         boost::static_pointer_cast<Option6IA>(advertise_pkt6->getOption(D6O_IA_NA));
1647     if (!opt_ia_na_advertise) {
1648         isc_throw(Unexpected, "DHCPv6 IA_NA option not found in received "
1649                   "packet");
1650     }
1651     size_t addr_offset = getRequestedIpOffset();
1652     boost::shared_ptr<LocalizedOption>
1653         opt_ia_na(new LocalizedOption(opt_ia_na_advertise, addr_offset));
1654     if (!opt_ia_na->valid()) {
1655         isc_throw(BadValue, "Option IA_NA in advertise packet is invalid");
1656     }
1657     pkt6->addOption(opt_ia_na);
1658     // Set server id.
1659     OptionPtr opt_serverid_advertise = advertise_pkt6->getOption(D6O_SERVERID);
1660     if (!opt_serverid_advertise) {
1661         isc_throw(Unexpected, "DHCPV6 SERVERID option not found in received "
1662                   "packet");
1663     }
1664     size_t srvid_offset = getServerIdOffset();
1665     boost::shared_ptr<LocalizedOption>
1666         opt_serverid(new LocalizedOption(Option::V6, D6O_SERVERID,
1667                                          opt_serverid_advertise->getData(),
1668                                          srvid_offset));
1669     pkt6->addOption(opt_serverid);
1670     // Get randomization offset.
1671     size_t rand_offset = getRandomOffset(arg_idx);
1672     OptionPtr opt_clientid_advertise = advertise_pkt6->getOption(D6O_CLIENTID);
1673     if (!opt_clientid_advertise) {
1674         isc_throw(Unexpected, "DHCPV6 CLIENTID option not found in received packet");
1675     }
1676     rand_offset -= (opt_clientid_advertise->len() - 1);
1677     // Set client id.
1678     boost::shared_ptr<LocalizedOption>
1679         opt_clientid(new LocalizedOption(Option::V6, D6O_CLIENTID,
1680                                          opt_clientid_advertise->getData(),
1681                                          rand_offset));
1682     pkt6->addOption(opt_clientid);
1683     // Set default packet data.
1684     setDefaults6(pkt6);
1685 
1686     // Add any extra options that user may have specified.
1687     addExtraOpts(pkt6);
1688 
1689     // Prepare on wire data.
1690     pkt6->rawPack();
1691     // Send packet.
1692     socket_.send(pkt6);
1693     // Update packet stats.
1694     stats_mgr_.passSentPacket(ExchangeType::RR, pkt6);
1695 
1696     // When 'T' diagnostics flag is specified it means that user requested
1697     // printing packet contents. It will be just one (first) packet which
1698     // contents will be printed. Here we check if this packet has been already
1699     // collected. If it hasn't we save this packet so as we can print its
1700     // contents when test is finished.
1701     if (options_.testDiags('T') &&
1702         (template_packets_v6_.find(DHCPV6_REQUEST) == template_packets_v6_.end())) {
1703         template_packets_v6_[DHCPV6_REQUEST] = pkt6;
1704     }
1705 }
1706 
1707 void
sendSolicit6(const bool preload)1708 TestControl::sendSolicit6(const bool preload /*= false*/) {
1709     // Generate DUID to be passed to the packet
1710     uint8_t randomized = 0;
1711     std::vector<uint8_t> duid = generateDuid(randomized);
1712     // Generate transaction id to be set for the new exchange.
1713     const uint32_t transid = generateTransid();
1714     Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, transid));
1715     if (!pkt6) {
1716         isc_throw(Unexpected, "failed to create SOLICIT packet");
1717     }
1718 
1719     // Check if we need to simulate HA failures by pretending no responses were received.
1720     // The DHCPv6 protocol signals that by increasing the elapsed option field. Note it is in 1/100 of a second.
1721     if (options_.getIncreaseElapsedTime() &&
1722         stats_mgr_.getTestPeriod().length().total_seconds() >= options_.getWaitForElapsedTime() &&
1723         stats_mgr_.getTestPeriod().length().total_seconds() < options_.getWaitForElapsedTime() +
1724                                      options_.getIncreaseElapsedTime()) {
1725 
1726 
1727         // Keep increasing elapsed time. The value should start increasing steadily.
1728         uint32_t val = (stats_mgr_.getTestPeriod().length().total_seconds() - options_.getWaitForElapsedTime() + 1)*100;
1729         if (val > 65535) {
1730             val = 65535;
1731         }
1732         OptionPtr elapsed(new OptionInt<uint16_t>(Option::V6, D6O_ELAPSED_TIME, val));
1733         pkt6->addOption(elapsed);
1734     } else {
1735         pkt6->addOption(Option::factory(Option::V6, D6O_ELAPSED_TIME));
1736     }
1737 
1738     if (options_.isRapidCommit()) {
1739         pkt6->addOption(Option::factory(Option::V6, D6O_RAPID_COMMIT));
1740     }
1741     pkt6->addOption(Option::factory(Option::V6, D6O_CLIENTID, duid));
1742     pkt6->addOption(Option::factory(Option::V6, D6O_ORO));
1743 
1744 
1745     // Depending on the lease-type option specified, we should request
1746     // IPv6 address (with IA_NA) or IPv6 prefix (IA_PD) or both.
1747 
1748     // IA_NA
1749     if (options_.getLeaseType().includes(CommandOptions::LeaseType::ADDRESS)) {
1750         pkt6->addOption(Option::factory(Option::V6, D6O_IA_NA));
1751     }
1752     // IA_PD
1753     if (options_.getLeaseType().includes(CommandOptions::LeaseType::PREFIX)) {
1754         pkt6->addOption(Option::factory(Option::V6, D6O_IA_PD));
1755     }
1756 
1757     setDefaults6(pkt6);
1758 
1759     // Add any extra options that user may have specified.
1760     addExtraOpts(pkt6);
1761 
1762     pkt6->pack();
1763     socket_.send(pkt6);
1764     if (!preload) {
1765         stats_mgr_.passSentPacket(ExchangeType::SA, pkt6);
1766     }
1767 
1768     saveFirstPacket(pkt6);
1769 }
1770 
1771 void
sendSolicit6(const std::vector<uint8_t> & template_buf,const bool preload)1772 TestControl::sendSolicit6(const std::vector<uint8_t>& template_buf,
1773                           const bool preload /*= false*/) {
1774     const int arg_idx = 0;
1775     // Get transaction id offset.
1776     size_t transid_offset = getTransactionIdOffset(arg_idx);
1777     // Generate transaction id to be set for the new exchange.
1778     const uint32_t transid = generateTransid();
1779     // Create packet.
1780     PerfPkt6Ptr pkt6(new PerfPkt6(&template_buf[0], template_buf.size(),
1781                                   transid_offset, transid));
1782     if (!pkt6) {
1783         isc_throw(Unexpected, "failed to create SOLICIT packet");
1784     }
1785     size_t rand_offset = getRandomOffset(arg_idx);
1786     // randomized will pick number of bytes randomized so we can
1787     // just use part of the generated duid and substitute a few bytes
1788     /// in template.
1789     uint8_t randomized = 0;
1790     std::vector<uint8_t> duid = generateDuid(randomized);
1791     if (rand_offset > template_buf.size()) {
1792         isc_throw(OutOfRange, "randomization offset is out of bounds");
1793     }
1794     // Store random part of the DUID into the packet.
1795     pkt6->writeAt(rand_offset - randomized + 1,
1796                   duid.end() - randomized, duid.end());
1797 
1798     // Prepare on-wire data.
1799     pkt6->rawPack();
1800     setDefaults6(pkt6);
1801 
1802     // Add any extra options that user may have specified.
1803     addExtraOpts(pkt6);
1804 
1805     // Send solicit packet.
1806     socket_.send(pkt6);
1807     if (!preload) {
1808         // Update packet stats.
1809         stats_mgr_.passSentPacket(ExchangeType::SA, pkt6);
1810     }
1811     saveFirstPacket(pkt6);
1812 }
1813 
1814 
1815 void
setDefaults4(const Pkt4Ptr & pkt)1816 TestControl::setDefaults4(const Pkt4Ptr& pkt) {
1817     // Interface name.
1818     IfacePtr iface = socket_.getIface();
1819     if (iface == NULL) {
1820         isc_throw(BadValue, "unable to find interface with given index");
1821     }
1822     pkt->setIface(iface->getName());
1823     // Interface index.
1824     pkt->setIndex(socket_.ifindex_);
1825     // Local client's port (68)
1826     pkt->setLocalPort(DHCP4_CLIENT_PORT);
1827     // Server's port (67)
1828     if (options_.getRemotePort()) {
1829         pkt->setRemotePort(options_.getRemotePort());
1830     } else {
1831         pkt->setRemotePort(DHCP4_SERVER_PORT);
1832     }
1833     // The remote server's name or IP.
1834     pkt->setRemoteAddr(IOAddress(options_.getServerName()));
1835     // Set local address.
1836     pkt->setLocalAddr(IOAddress(socket_.addr_));
1837     // Set relay (GIADDR) address to local address if multiple
1838     // subnet mode is not enabled
1839     if (!options_.checkMultiSubnet()) {
1840         pkt->setGiaddr(IOAddress(socket_.addr_));
1841     } else {
1842         pkt->setGiaddr(IOAddress(options_.getRandRelayAddr()));
1843     }
1844     // Pretend that we have one relay (which is us).
1845     pkt->setHops(1);
1846 }
1847 
1848 void
setDefaults6(const Pkt6Ptr & pkt)1849 TestControl::setDefaults6(const Pkt6Ptr& pkt) {
1850     // Interface name.
1851     IfacePtr iface = socket_.getIface();
1852     if (iface == NULL) {
1853         isc_throw(BadValue, "unable to find interface with given index");
1854     }
1855     pkt->setIface(iface->getName());
1856     // Interface index.
1857     pkt->setIndex(socket_.ifindex_);
1858     // Local client's port (547)
1859     pkt->setLocalPort(DHCP6_CLIENT_PORT);
1860     // Server's port (548)
1861     if (options_.getRemotePort()) {
1862         pkt->setRemotePort(options_.getRemotePort());
1863     } else {
1864         pkt->setRemotePort(DHCP6_SERVER_PORT);
1865     }
1866     // Set local address.
1867     pkt->setLocalAddr(socket_.addr_);
1868     // The remote server's name or IP.
1869     pkt->setRemoteAddr(IOAddress(options_.getServerName()));
1870 
1871     // only act as a relay agent when told so.
1872     /// @todo: support more level of encapsulation, at the moment we only support
1873     /// one, via -A1 option.
1874     if (options_.isUseRelayedV6()) {
1875       Pkt6::RelayInfo relay_info;
1876       relay_info.msg_type_ = DHCPV6_RELAY_FORW;
1877       relay_info.hop_count_ = 0;
1878       if (options_.checkMultiSubnet()) {
1879           relay_info.linkaddr_ = IOAddress(options_.getRandRelayAddr());
1880       } else {
1881           relay_info.linkaddr_ = IOAddress(socket_.addr_);
1882       }
1883       relay_info.peeraddr_ = IOAddress(socket_.addr_);
1884       pkt->addRelayInfo(relay_info);
1885     }
1886 }
1887 
1888 namespace {
1889 
concatenateBuffers(OptionBuffer const & a,OptionBuffer const & b)1890 static OptionBuffer const concatenateBuffers(OptionBuffer const& a,
1891                                              OptionBuffer const& b) {
1892     OptionBuffer result;
1893     result.insert(result.end(), a.begin(), a.end());
1894     result.insert(result.end(), b.begin(), b.end());
1895     return result;
1896 }
1897 
mergeOptionIntoPacket(Pkt4Ptr const & packet,OptionPtr const & extra_option)1898 static void mergeOptionIntoPacket(Pkt4Ptr const& packet,
1899                                   OptionPtr const& extra_option) {
1900     uint16_t const code(extra_option->getType());
1901     // If option already exists...
1902     OptionPtr const& option(packet->getOption(code));
1903     if (option) {
1904         switch (code) {
1905         // List here all the options for which we want to concatenate buffers.
1906         case DHO_DHCP_PARAMETER_REQUEST_LIST:
1907             packet->delOption(code);
1908             packet->addOption(boost::make_shared<Option>(
1909                 Option::V4, code,
1910                 concatenateBuffers(option->getData(),
1911                                    extra_option->getData())));
1912             return;
1913         default:
1914             // For all others, add option as usual, it will result in "Option
1915             // already present in this message" error.
1916             break;
1917         }
1918     }
1919     packet->addOption(extra_option);
1920 }
1921 
1922 }  // namespace
1923 
1924 void
addExtraOpts(const Pkt4Ptr & pkt)1925 TestControl::addExtraOpts(const Pkt4Ptr& pkt) {
1926     // Add all extra options that the user may have specified.
1927     const dhcp::OptionCollection& extra_opts = options_.getExtraOpts();
1928     for (auto entry : extra_opts) {
1929         mergeOptionIntoPacket(pkt, entry.second);
1930     }
1931 }
1932 
1933 void
addExtraOpts(const Pkt6Ptr & pkt)1934 TestControl::addExtraOpts(const Pkt6Ptr& pkt) {
1935     // Add all extra options that the user may have specified.
1936     const dhcp::OptionCollection& extra_opts = options_.getExtraOpts();
1937     for (auto entry : extra_opts) {
1938         pkt->addOption(entry.second);
1939     }
1940 }
1941 
1942 }  // namespace perfdhcp
1943 }  // namespace isc
1944