Kea  2.3.9
test_control.cc
Go to the documentation of this file.
1 // Copyright (C) 2012-2023 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 
10 #include <perfdhcp/receiver.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>
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
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
69 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
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.
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
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
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
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()) {
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()) {
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,
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) {
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
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
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  }
278  "elapsed time option buffer size has to be 0 or 2");
279 }
280 
281 OptionPtr
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
289 TestControl::factoryIana6(Option::Universe, uint16_t,
290  const OptionBuffer& buf) {
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
305 TestControl::factoryIapd6(Option::Universe, uint16_t,
306  const OptionBuffer& buf) {
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
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
327 TestControl::factoryOptionRequestOption6(Option::Universe,
328  uint16_t,
329  const OptionBuffer&) {
330  const uint8_t buf_array[] = {
331  0, D6O_NAME_SERVERS,
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
340 TestControl::factoryRequestList4(Option::Universe u,
341  uint16_t type,
342  const OptionBuffer& buf) {
343  const uint8_t buf_array[] = {
347  DHO_ROUTERS,
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>
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
406 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>
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());
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
461 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
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
486 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
496 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
506 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 
516 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
524 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
534 TestControl::handleChild(int) {
535  int status = 0;
536  while (wait3(&status, WNOHANG, NULL) > 0) {
537  // continue
538  }
539 }
540 
541 void
542 TestControl::handleInterrupt(int) {
543  interrupted_ = true;
544 }
545 
546 void
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
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 {
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 {
580  sendSolicit6(template_buffers_[0], preload);
581  }
582  }
583  }
584 }
585 
586 uint64_t
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
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
609 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
623 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
680 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
691 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
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
741 TestControl::printStats() const {
742  printRate();
743  stats_mgr_.printStats();
744  if (options_.testDiags('i')) {
745  stats_mgr_.printCustomCounters();
746  }
747 }
748 
749 std::string
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
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
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 {
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
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 (const auto& opt : pkt6->options_) {
865  switch (opt.second->getType()) {
866  case D6O_IA_PD: {
867  // add address and check if it has not been already assigned
868  // addresses should be unique cross options of the packet
869  auto ret = current.emplace(boost::dynamic_pointer_cast<
870  Option6IAPrefix>(opt.second->getOption(D6O_IAPREFIX))->getAddress().toText());
871  if (!ret.second) {
872  stats_mgr_.updateNonUniqueAddrNum(xchg_type);
873  }
874  break;
875  }
876  case D6O_IA_NA: {
877  // add address and check if it has not been already assigned
878  // addresses should be unique cross options of the packet
879  auto ret = current.emplace(boost::dynamic_pointer_cast<
880  Option6IAAddr>(opt.second->getOption(D6O_IAADDR))->getAddress().toText());
881  if (!ret.second) {
882  stats_mgr_.updateNonUniqueAddrNum(xchg_type);
883  }
884  break;
885  }
886  default:
887  break;
888  }
889  }
890  // addresses should be unique cross packets
891  addUniqeAddr(current, xchg_type);
892  }
893 }
894 
895 void
896 TestControl::address4Uniqueness(const Pkt4Ptr& pkt4, ExchangeType xchg_type) {
897  // check if received address is unique
898  if (options_.getAddrUnique()) {
899  // addresses were already checked in validateIA
900  // we can safely assume that those are correct
901  std::set<std::string> current;
902  current.insert(pkt4->getYiaddr().toText());
903  // addresses should be unique cross packets
904  addUniqeAddr(current, xchg_type);
905  }
906 }
907 
908 bool
909 TestControl::validateIA(const Pkt6Ptr& pkt6) {
910  // check if iaaddr exists - if it does, we can continue sending request
911  // if not we will update statistics about rejected leases
912  // @todo it's checking just one iaaddress option for now it's ok
913  // but when perfdhcp will be extended to create message with multiple IA
914  // this will have to be iterate on:
915  // OptionCollection ias = pkt6->getOptions(D6O_IA_NA);
916  Option6IAPrefixPtr iapref;
917  Option6IAAddrPtr iaaddr;
918  if (pkt6->getOption(D6O_IA_PD)) {
919  iapref = boost::dynamic_pointer_cast<
920  Option6IAPrefix>(pkt6->getOption(D6O_IA_PD)->getOption(D6O_IAPREFIX));
921  }
922  if (pkt6->getOption(D6O_IA_NA)) {
923  iaaddr = boost::dynamic_pointer_cast<
924  Option6IAAddr>(pkt6->getOption(D6O_IA_NA)->getOption(D6O_IAADDR));
925  }
926 
927  bool address_and_prefix = options_.getLeaseType().includes(
928  CommandOptions::LeaseType::ADDRESS_AND_PREFIX);
929  bool prefix_only = options_.getLeaseType().includes(
930  CommandOptions::LeaseType::PREFIX);
931  bool address_only = options_.getLeaseType().includes(
932  CommandOptions::LeaseType::ADDRESS);
933  if ((address_and_prefix && iapref && iaaddr) ||
934  (prefix_only && iapref && !address_and_prefix) ||
935  (address_only && iaaddr && !address_and_prefix)) {
936  return true;
937  } else {
938  return false;
939  }
940 }
941 
942 void
943 TestControl::processReceivedPacket6(const Pkt6Ptr& pkt6) {
944  uint8_t packet_type = pkt6->getType();
945  if (packet_type == DHCPV6_ADVERTISE) {
946  PktPtr pkt = stats_mgr_.passRcvdPacket(ExchangeType::SA, pkt6);
947  Pkt6Ptr solicit_pkt6(boost::dynamic_pointer_cast<Pkt6>(pkt));
948  CommandOptions::ExchangeMode xchg_mode = options_.getExchangeMode();
949  if ((xchg_mode == CommandOptions::DORA_SARR) && solicit_pkt6) {
950  if (validateIA(pkt6)) {
951  // if address is correct - check uniqueness
952  address6Uniqueness(pkt6, ExchangeType::SA);
953  if (template_buffers_.size() < 2) {
954  sendRequest6(pkt6);
955  } else {
958  sendRequest6(template_buffers_[1], pkt6);
959  }
960  } else {
961  stats_mgr_.updateRejLeases(ExchangeType::SA);
962  }
963  }
964  } else if (packet_type == DHCPV6_REPLY) {
965  // If the received message is Reply, we have to find out which exchange
966  // type the Reply message belongs to. It is doable by matching the Reply
967  // transaction id with the transaction id of the sent Request, Renew
968  // or Release. First we start with the Request.
969  if (stats_mgr_.passRcvdPacket(ExchangeType::RR, pkt6)) {
970  // The Reply belongs to Request-Reply exchange type. So, we may need
971  // to keep this Reply in the storage if Renews or/and Releases are
972  // being sent. Note that, Reply messages hold the information about
973  // leases assigned. We use this information to construct Renew and
974  // Release messages.
975  if (validateIA(pkt6)) {
976  // if address is correct - check uniqueness
977  address6Uniqueness(pkt6, ExchangeType::RR);
978  // check if there is correct IA to continue with Renew/Release
979  if (stats_mgr_.hasExchangeStats(ExchangeType::RN) ||
980  stats_mgr_.hasExchangeStats(ExchangeType::RL)) {
981  // Renew or Release messages are sent, because StatsMgr has the
982  // specific exchange type specified. Let's append the Reply
983  // message to a storage.
984  reply_storage_.append(pkt6);
985  }
986  } else {
987  stats_mgr_.updateRejLeases(ExchangeType::RR);
988  }
989  // The Reply message is not a server's response to the Request message
990  // sent within the 4-way exchange. It may be a response to the Renew
991  // or Release message. In the if clause we first check if StatsMgr
992  // has exchange type for Renew specified, and if it has, if there is
993  // a corresponding Renew message for the received Reply. If not,
994  // we check that StatsMgr has exchange type for Release specified,
995  // as possibly the Reply has been sent in response to Release.
996  } else if (!(stats_mgr_.hasExchangeStats(ExchangeType::RN) &&
997  stats_mgr_.passRcvdPacket(ExchangeType::RN, pkt6)) &&
998  stats_mgr_.hasExchangeStats(ExchangeType::RL)) {
999  // At this point, it is only possible that the Reply has been sent
1000  // in response to a Release. Try to match the Reply with Release.
1001  stats_mgr_.passRcvdPacket(ExchangeType::RL, pkt6);
1002  }
1003  }
1004 }
1005 
1006 unsigned int
1007 TestControl::consumeReceivedPackets() {
1008  unsigned int pkt_count = 0;
1009  PktPtr pkt;
1010  while ((pkt = receiver_.getPkt())) {
1011  pkt_count += 1;
1012  if (options_.getIpVersion() == 4) {
1013  Pkt4Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4>(pkt);
1014  processReceivedPacket4(pkt4);
1015  } else {
1016  Pkt6Ptr pkt6 = boost::dynamic_pointer_cast<Pkt6>(pkt);
1017  processReceivedPacket6(pkt6);
1018  }
1019  }
1020  return pkt_count;
1021 }
1022 void
1023 TestControl::registerOptionFactories4() const {
1024  static bool factories_registered = false;
1025  if (!factories_registered) {
1026  // DHCP_MESSAGE_TYPE option factory.
1027  LibDHCP::OptionFactoryRegister(Option::V4,
1029  &TestControl::factoryGeneric);
1030  // DHCP_SERVER_IDENTIFIER option factory.
1031  LibDHCP::OptionFactoryRegister(Option::V4,
1033  &TestControl::factoryGeneric);
1034  // DHCP_PARAMETER_REQUEST_LIST option factory.
1035  LibDHCP::OptionFactoryRegister(Option::V4,
1037  &TestControl::factoryRequestList4);
1038  }
1039  factories_registered = true;
1040 }
1041 
1042 void
1043 TestControl::registerOptionFactories6() const {
1044  static bool factories_registered = false;
1045  if (!factories_registered) {
1046  // D6O_ELAPSED_TIME
1047  LibDHCP::OptionFactoryRegister(Option::V6,
1049  &TestControl::factoryElapsedTime6);
1050  // D6O_RAPID_COMMIT
1051  LibDHCP::OptionFactoryRegister(Option::V6,
1053  &TestControl::factoryRapidCommit6);
1054  // D6O_ORO (option request option) factory.
1055  LibDHCP::OptionFactoryRegister(Option::V6,
1056  D6O_ORO,
1057  &TestControl::factoryOptionRequestOption6);
1058  // D6O_CLIENTID option factory.
1059  LibDHCP::OptionFactoryRegister(Option::V6,
1060  D6O_CLIENTID,
1061  &TestControl::factoryGeneric);
1062  // D6O_SERVERID option factory.
1063  LibDHCP::OptionFactoryRegister(Option::V6,
1064  D6O_SERVERID,
1065  &TestControl::factoryGeneric);
1066  // D6O_IA_NA option factory.
1067  LibDHCP::OptionFactoryRegister(Option::V6,
1068  D6O_IA_NA,
1069  &TestControl::factoryIana6);
1070 
1071  // D6O_IA_PD option factory.
1072  LibDHCP::OptionFactoryRegister(Option::V6,
1073  D6O_IA_PD,
1074  &TestControl::factoryIapd6);
1075 
1076 
1077  }
1078  factories_registered = true;
1079 }
1080 
1081 void
1082 TestControl::registerOptionFactories() const {
1083  switch(options_.getIpVersion()) {
1084  case 4:
1085  registerOptionFactories4();
1086  break;
1087  case 6:
1088  registerOptionFactories6();
1089  break;
1090  default:
1091  isc_throw(InvalidOperation, "command line options have to be parsed "
1092  "before DHCP option factories can be registered");
1093  }
1094 }
1095 
1096 void
1097 TestControl::reset() {
1098  transid_gen_.reset();
1099  last_report_ = microsec_clock::universal_time();
1100  // Actual generators will have to be set later on because we need to
1101  // get command line parameters first.
1102  setTransidGenerator(NumberGeneratorPtr());
1103  setMacAddrGenerator(NumberGeneratorPtr());
1104  first_packet_serverid_.clear();
1105  interrupted_ = false;
1106 }
1107 
1108 TestControl::TestControl(CommandOptions& options, BasePerfSocket &socket) :
1109  exit_time_(not_a_date_time),
1110  number_generator_(0, options.getMacsFromFile().size()),
1111  socket_(socket),
1112  receiver_(socket, options.isSingleThreaded(), options.getIpVersion()),
1113  stats_mgr_(options),
1114  options_(options)
1115 {
1116  // Reset singleton state before test starts.
1117  reset();
1118 
1119  // Ip version is not set ONLY in case the command options
1120  // were not parsed. This surely means that parse() function
1121  // was not called prior to starting the test. This is fatal
1122  // error.
1123  if (options_.getIpVersion() == 0) {
1125  "command options must be parsed before running a test");
1126  } else if (options_.getIpVersion() == 4) {
1127  // Turn off packet queueing.
1128  IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, data::ElementPtr());
1130  } else {
1131  // Turn off packet queueing.
1132  IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, data::ElementPtr());
1134  }
1135 
1136  uint32_t clients_num = options_.getClientsNum() == 0 ?
1137  1 : options_.getClientsNum();
1139 
1140  // Diagnostics are command line options mainly.
1141  printDiagnostics();
1142  // Option factories have to be registered.
1144  // Initialize packet templates.
1146  // Initialize randomization seed.
1147  if (options_.isSeeded()) {
1148  srandom(options_.getSeed());
1149  } else {
1150  // Seed with current time.
1151  time_period duration(from_iso_string("20111231T235959"),
1152  microsec_clock::universal_time());
1153  srandom(duration.length().total_seconds()
1154  + duration.length().fractional_seconds());
1155  }
1156  // If user interrupts the program we will exit gracefully.
1157  signal(SIGINT, TestControl::handleInterrupt);
1158 }
1159 
1160 void
1161 TestControl::runWrapped(bool do_stop /*= false */) const {
1162  if (!options_.getWrapped().empty()) {
1163  pid_t pid = 0;
1164  signal(SIGCHLD, handleChild);
1165  pid = fork();
1166  if (pid < 0) {
1167  isc_throw(Unexpected, "unable to fork");
1168  } else if (pid == 0) {
1169  execlp(options_.getWrapped().c_str(), do_stop ? "stop" : "start", (void*)0);
1170  }
1171  }
1172 }
1173 
1174 void
1176  if (options_.testDiags('T')) {
1177  if (template_packets_v4_.find(pkt->getType()) == template_packets_v4_.end()) {
1178  template_packets_v4_[pkt->getType()] = pkt;
1179  }
1180  }
1181 }
1182 
1183 void
1185  if (options_.testDiags('T')) {
1186  if (template_packets_v6_.find(pkt->getType()) == template_packets_v6_.end()) {
1187  template_packets_v6_[pkt->getType()] = pkt;
1188  }
1189  }
1190 }
1191 
1192 void
1193 TestControl::sendDiscover4(const bool preload /*= false*/) {
1194  // Generate the MAC address to be passed in the packet.
1195  uint8_t randomized = 0;
1196  std::vector<uint8_t> mac_address = generateMacAddress(randomized);
1197  // Generate transaction id to be set for the new exchange.
1198  const uint32_t transid = generateTransid();
1199  Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, transid));
1200  if (!pkt4) {
1201  isc_throw(Unexpected, "failed to create DISCOVER packet");
1202  }
1203 
1204  // Delete the default Message Type option set by Pkt4
1205  pkt4->delOption(DHO_DHCP_MESSAGE_TYPE);
1206 
1207  // Set options: DHCP_MESSAGE_TYPE and DHCP_PARAMETER_REQUEST_LIST
1208  OptionBuffer buf_msg_type;
1209  buf_msg_type.push_back(DHCPDISCOVER);
1210  pkt4->addOption(Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE,
1211  buf_msg_type));
1212  pkt4->addOption(Option::factory(Option::V4,
1214 
1215 
1216  // Set client's and server's ports as well as server's address,
1217  // and local (relay) address.
1218  setDefaults4(pkt4);
1219 
1220  // Set hardware address
1221  pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address);
1222 
1223  // Set client identifier
1224  pkt4->addOption(generateClientId(pkt4->getHWAddr()));
1225 
1226  // Check if we need to simulate HA failures by pretending no responses were received.
1227  // The DHCP protocol signals that by increasing secs field (seconds since the configuration attempt started).
1229  stats_mgr_.getTestPeriod().length().total_seconds() >= options_.getWaitForElapsedTime() &&
1230  stats_mgr_.getTestPeriod().length().total_seconds() < options_.getWaitForElapsedTime() +
1232 
1233  // Keep increasing elapsed time. The value should start increasing steadily.
1234  uint32_t val = stats_mgr_.getTestPeriod().length().total_seconds() - options_.getWaitForElapsedTime() + 1;
1235  if (val > 65535) {
1236  val = 65535;
1237  }
1238  pkt4->setSecs(static_cast<uint16_t>(val));
1239  }
1240 
1241  // Add any extra options that user may have specified.
1242  addExtraOpts(pkt4);
1243 
1244  pkt4->pack();
1245  socket_.send(pkt4);
1246  if (!preload) {
1248  }
1249 
1250  saveFirstPacket(pkt4);
1251 }
1252 
1253 void
1254 TestControl::sendDiscover4(const std::vector<uint8_t>& template_buf,
1255  const bool preload /* = false */) {
1256  // Get the first argument if multiple the same arguments specified
1257  // in the command line. First one refers to DISCOVER packets.
1258  const uint8_t arg_idx = 0;
1259  // Generate the MAC address to be passed in the packet.
1260  uint8_t randomized = 0;
1261  std::vector<uint8_t> mac_address = generateMacAddress(randomized);
1262  // Generate transaction id to be set for the new exchange.
1263  const uint32_t transid = generateTransid();
1264  // Get transaction id offset.
1265  size_t transid_offset = getTransactionIdOffset(arg_idx);
1266  // Get randomization offset.
1267  // We need to go back by HW_ETHER_LEN (MAC address length)
1268  // because this offset points to last octet of MAC address.
1269  size_t rand_offset = getRandomOffset(arg_idx) - HW_ETHER_LEN + 1;
1270  // Create temporary buffer with template contents. We will
1271  // modify this temporary buffer but we don't want to modify
1272  // the original template.
1273  std::vector<uint8_t> in_buf(template_buf.begin(),
1274  template_buf.end());
1275  // Check if we are not going out of bounds.
1276  if (rand_offset + HW_ETHER_LEN > in_buf.size()) {
1277  isc_throw(OutOfRange, "randomization offset is out of bounds");
1278  }
1279  PerfPkt4Ptr pkt4(new PerfPkt4(&in_buf[0], in_buf.size(),
1280  transid_offset,
1281  transid));
1282 
1283  // Replace MAC address in the template with actual MAC address.
1284  pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end());
1285  // Create a packet from the temporary buffer.
1286  setDefaults4(boost::static_pointer_cast<Pkt4>(pkt4));
1287  // Pack the input packet buffer to output buffer so as it can
1288  // be sent to server.
1289  pkt4->rawPack();
1290  socket_.send(boost::static_pointer_cast<Pkt4>(pkt4));
1291  if (!preload) {
1292  // Update packet stats.
1294  boost::static_pointer_cast<Pkt4>(pkt4));
1295  }
1296  saveFirstPacket(pkt4);
1297 }
1298 
1299 bool
1300 TestControl::sendMessageFromAck(const uint16_t msg_type) {
1301  // We only permit Request or Release messages to be sent using this
1302  // function.
1303  if (msg_type != DHCPREQUEST && msg_type != DHCPRELEASE) {
1305  "invalid message type "
1306  << msg_type
1307  << " to be sent, expected DHCPREQUEST or DHCPRELEASE");
1308  }
1309 
1310  // Get one of the recorded DHCPACK messages.
1311  Pkt4Ptr ack = ack_storage_.getRandom();
1312  if (!ack) {
1313  return (false);
1314  }
1315 
1316  // Create message of the specified type.
1317  Pkt4Ptr msg = createMessageFromAck(msg_type, ack);
1318  setDefaults4(msg);
1319 
1320  // Override relay address
1321  msg->setGiaddr(ack->getGiaddr());
1322 
1323  // Add any extra options that user may have specified.
1324  addExtraOpts(msg);
1325 
1326  // Pack it.
1327  msg->pack();
1328 
1329  // And send it.
1330  socket_.send(msg);
1334  msg);
1335  return (true);
1336 }
1337 
1338 
1339 bool
1340 TestControl::sendMessageFromReply(const uint16_t msg_type) {
1341  // We only permit Release or Renew messages to be sent using this function.
1342  if (msg_type != DHCPV6_RENEW && msg_type != DHCPV6_RELEASE) {
1343  isc_throw(isc::BadValue, "invalid message type " << msg_type
1344  << " to be sent, expected DHCPV6_RENEW or DHCPV6_RELEASE");
1345  }
1346 
1347  // Get one of the recorded DHCPV6_OFFER messages.
1348  Pkt6Ptr reply = reply_storage_.getRandom();
1349  if (!reply) {
1350  return (false);
1351  }
1352  // Prepare the message of the specified type.
1353  Pkt6Ptr msg = createMessageFromReply(msg_type, reply);
1354  setDefaults6(msg);
1355 
1356  // Add any extra options that user may have specified.
1357  addExtraOpts(msg);
1358 
1359  // Pack it.
1360  msg->pack();
1361 
1362  // And send it.
1363  socket_.send(msg);
1366  : ExchangeType::RL), msg);
1367  return (true);
1368 }
1369 
1370 void
1372  const dhcp::Pkt4Ptr& offer_pkt4) {
1373  // Use the same transaction id as the one used in the discovery packet.
1374  const uint32_t transid = discover_pkt4->getTransid();
1375  Pkt4Ptr pkt4(new Pkt4(DHCPREQUEST, transid));
1376 
1377  // Use first flags indicates that we want to use the server
1378  // id captured in first packet.
1379  if (options_.isUseFirst() &&
1380  (first_packet_serverid_.size() > 0)) {
1381  pkt4->addOption(Option::factory(Option::V4, DHO_DHCP_SERVER_IDENTIFIER,
1383  } else {
1384  OptionPtr opt_serverid =
1385  offer_pkt4->getOption(DHO_DHCP_SERVER_IDENTIFIER);
1386  if (!opt_serverid) {
1387  isc_throw(BadValue, "there is no SERVER_IDENTIFIER option "
1388  << "in OFFER message");
1389  }
1391  first_packet_serverid_ = opt_serverid->getData();
1392  }
1393  pkt4->addOption(opt_serverid);
1394  }
1395 
1397  asiolink::IOAddress yiaddr = offer_pkt4->getYiaddr();
1398  if (!yiaddr.isV4()) {
1399  isc_throw(BadValue, "the YIADDR returned in OFFER packet is not "
1400  " IPv4 address");
1401  }
1402  OptionPtr opt_requested_address =
1404  OptionBuffer()));
1405  opt_requested_address->setUint32(yiaddr.toUint32());
1406  pkt4->addOption(opt_requested_address);
1407  OptionPtr opt_parameter_list =
1408  Option::factory(Option::V4, DHO_DHCP_PARAMETER_REQUEST_LIST);
1409  pkt4->addOption(opt_parameter_list);
1410  // Set client's and server's ports as well as server's address
1411  setDefaults4(pkt4);
1412  // Override relay address
1413  pkt4->setGiaddr(offer_pkt4->getGiaddr());
1414  // Add any extra options that user may have specified.
1415  addExtraOpts(pkt4);
1416 
1417  // Set hardware address
1418  pkt4->setHWAddr(offer_pkt4->getHWAddr());
1419  // Set client id.
1420  pkt4->addOption(generateClientId(pkt4->getHWAddr()));
1421  // Set elapsed time.
1422  uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4);
1423  pkt4->setSecs(static_cast<uint16_t>(elapsed_time / 1000));
1424  // Prepare on wire data to send.
1425  pkt4->pack();
1426  socket_.send(pkt4);
1428  saveFirstPacket(pkt4);
1429 }
1430 
1431 void
1432 TestControl::sendRequest4(const std::vector<uint8_t>& template_buf,
1433  const dhcp::Pkt4Ptr& discover_pkt4,
1434  const dhcp::Pkt4Ptr& offer_pkt4) {
1435  // Get the second argument if multiple the same arguments specified
1436  // in the command line. Second one refers to REQUEST packets.
1437  const uint8_t arg_idx = 1;
1438  // Use the same transaction id as the one used in the discovery packet.
1439  const uint32_t transid = discover_pkt4->getTransid();
1440  // Get transaction id offset.
1441  size_t transid_offset = getTransactionIdOffset(arg_idx);
1442  // Get the offset of MAC's last octet.
1443  // We need to go back by HW_ETHER_LEN (MAC address length)
1444  // because this offset points to last octet of MAC address.
1445  size_t rand_offset = getRandomOffset(arg_idx) - HW_ETHER_LEN + 1;
1446  // Create temporary buffer from the template.
1447  std::vector<uint8_t> in_buf(template_buf.begin(),
1448  template_buf.end());
1449  // Check if given randomization offset is not out of bounds.
1450  if (rand_offset + HW_ETHER_LEN > in_buf.size()) {
1451  isc_throw(OutOfRange, "randomization offset is out of bounds");
1452  }
1453 
1454  // Create packet from the temporary buffer.
1455  PerfPkt4Ptr pkt4(new PerfPkt4(&in_buf[0], in_buf.size(),
1456  transid_offset,
1457  transid));
1458 
1459  // Set hardware address from OFFER packet received.
1460  HWAddrPtr hwaddr = offer_pkt4->getHWAddr();
1461  std::vector<uint8_t> mac_address(HW_ETHER_LEN, 0);
1462  uint8_t hw_len = hwaddr->hwaddr_.size();
1463  if (hw_len != 0) {
1464  memcpy(&mac_address[0], &hwaddr->hwaddr_[0],
1465  hw_len > HW_ETHER_LEN ? HW_ETHER_LEN : hw_len);
1466  }
1467  pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end());
1468 
1469  // Set elapsed time.
1470  size_t elp_offset = getElapsedTimeOffset();
1471  uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4);
1472  pkt4->writeValueAt<uint16_t>(elp_offset,
1473  static_cast<uint16_t>(elapsed_time / 1000));
1474 
1475  // Get the actual server id offset.
1476  size_t sid_offset = getServerIdOffset();
1477  // Use first flags indicates that we want to use the server
1478  // id captured in first packet.
1479  if (options_.isUseFirst() &&
1480  (first_packet_serverid_.size() > 0)) {
1481  boost::shared_ptr<LocalizedOption>
1482  opt_serverid(new LocalizedOption(Option::V4,
1485  sid_offset));
1486  pkt4->addOption(opt_serverid);
1487  } else {
1488  // Copy the contents of server identifier received in
1489  // OFFER packet to put this into REQUEST.
1490  OptionPtr opt_serverid_offer =
1491  offer_pkt4->getOption(DHO_DHCP_SERVER_IDENTIFIER);
1492  if (!opt_serverid_offer) {
1493  isc_throw(BadValue, "there is no SERVER_IDENTIFIER option "
1494  << "in OFFER message");
1495  }
1496  boost::shared_ptr<LocalizedOption>
1497  opt_serverid(new LocalizedOption(Option::V4,
1499  opt_serverid_offer->getData(),
1500  sid_offset));
1501  pkt4->addOption(opt_serverid);
1503  first_packet_serverid_ = opt_serverid_offer->getData();
1504  }
1505  }
1506 
1508  asiolink::IOAddress yiaddr = offer_pkt4->getYiaddr();
1509  if (!yiaddr.isV4()) {
1510  isc_throw(BadValue, "the YIADDR returned in OFFER packet is not "
1511  " IPv4 address");
1512  }
1513 
1514  // Get the actual offset of requested ip.
1515  size_t rip_offset = getRequestedIpOffset();
1516  // Place requested IP option at specified position (rip_offset).
1517  boost::shared_ptr<LocalizedOption>
1518  opt_requested_ip(new LocalizedOption(Option::V4,
1520  OptionBuffer(),
1521  rip_offset));
1522  // The IOAddress is convertible to uint32_t and returns exactly what we need.
1523  opt_requested_ip->setUint32(yiaddr.toUint32());
1524  pkt4->addOption(opt_requested_ip);
1525 
1526  setDefaults4(boost::static_pointer_cast<Pkt4>(pkt4));
1527 
1528  // Add any extra options that user may have specified.
1529  addExtraOpts(pkt4);
1530 
1531  // Prepare on-wire data.
1532  pkt4->rawPack();
1533  socket_.send(boost::static_pointer_cast<Pkt4>(pkt4));
1534  // Update packet stats.
1536  boost::static_pointer_cast<Pkt4>(pkt4));
1537  saveFirstPacket(pkt4);
1538 }
1539 
1540 void
1541 TestControl::sendRequest6(const Pkt6Ptr& advertise_pkt6) {
1542  const uint32_t transid = generateTransid();
1543  Pkt6Ptr pkt6(new Pkt6(DHCPV6_REQUEST, transid));
1544  // Set elapsed time.
1545  OptionPtr opt_elapsed_time =
1546  Option::factory(Option::V6, D6O_ELAPSED_TIME);
1547  pkt6->addOption(opt_elapsed_time);
1548  // Set client id.
1549  OptionPtr opt_clientid = advertise_pkt6->getOption(D6O_CLIENTID);
1550  if (!opt_clientid) {
1551  isc_throw(Unexpected, "client id not found in received packet");
1552  }
1553  pkt6->addOption(opt_clientid);
1554 
1555  // Use first flags indicates that we want to use the server
1556  // id captured in first packet.
1557  if (options_.isUseFirst() &&
1558  (first_packet_serverid_.size() > 0)) {
1559  pkt6->addOption(Option::factory(Option::V6, D6O_SERVERID,
1561  } else {
1562  OptionPtr opt_serverid = advertise_pkt6->getOption(D6O_SERVERID);
1563  if (!opt_serverid) {
1564  isc_throw(Unexpected, "server id not found in received packet");
1565  }
1567  first_packet_serverid_ = opt_serverid->getData();
1568  }
1569  pkt6->addOption(opt_serverid);
1570  }
1571 
1572  // Copy IA_NA or IA_PD option from the Advertise message to the Request
1573  // message being sent to the server. This will throw exception if the
1574  // option to be copied is not found. Note that this function will copy
1575  // one of IA_NA or IA_PD options, depending on the lease-type value
1576  // specified in the command line.
1577  copyIaOptions(advertise_pkt6, pkt6);
1578 
1579  // Set default packet data.
1580  setDefaults6(pkt6);
1581 
1582  // Add any extra options that user may have specified.
1583  addExtraOpts(pkt6);
1584 
1585  // Prepare on-wire data.
1586  pkt6->pack();
1587  socket_.send(pkt6);
1589  saveFirstPacket(pkt6);
1590 }
1591 
1592 void
1593 TestControl::sendRequest6(const std::vector<uint8_t>& template_buf,
1594  const Pkt6Ptr& advertise_pkt6) {
1595  // Get the second argument if multiple the same arguments specified
1596  // in the command line. Second one refers to REQUEST packets.
1597  const uint8_t arg_idx = 1;
1598  // Generate transaction id.
1599  const uint32_t transid = generateTransid();
1600  // Get transaction id offset.
1601  size_t transid_offset = getTransactionIdOffset(arg_idx);
1602  PerfPkt6Ptr pkt6(new PerfPkt6(&template_buf[0], template_buf.size(),
1603  transid_offset, transid));
1604  // Set elapsed time.
1605  size_t elp_offset = getElapsedTimeOffset();
1606  boost::shared_ptr<LocalizedOption>
1607  opt_elapsed_time(new LocalizedOption(Option::V6, D6O_ELAPSED_TIME,
1608  OptionBuffer(), elp_offset));
1609  pkt6->addOption(opt_elapsed_time);
1610 
1611  // Get the actual server id offset.
1612  size_t sid_offset = getServerIdOffset();
1613  // Use first flags indicates that we want to use the server
1614  // id captured in first packet.
1615  if (options_.isUseFirst() &&
1616  (first_packet_serverid_.size() > 0)) {
1617  boost::shared_ptr<LocalizedOption>
1618  opt_serverid(new LocalizedOption(Option::V6,
1619  D6O_SERVERID,
1621  sid_offset));
1622  pkt6->addOption(opt_serverid);
1623 
1624  } else {
1625  // Copy the contents of server identifier received in
1626  // ADVERTISE packet to put this into REQUEST.
1627  OptionPtr opt_serverid_advertise =
1628  advertise_pkt6->getOption(D6O_SERVERID);
1629  if (!opt_serverid_advertise) {
1630  isc_throw(BadValue, "there is no SERVERID option "
1631  << "in ADVERTISE message");
1632  }
1633  boost::shared_ptr<LocalizedOption>
1634  opt_serverid(new LocalizedOption(Option::V6,
1635  D6O_SERVERID,
1636  opt_serverid_advertise->getData(),
1637  sid_offset));
1638  pkt6->addOption(opt_serverid);
1640  first_packet_serverid_ = opt_serverid_advertise->getData();
1641  }
1642  }
1643  // Set IA_NA
1644  boost::shared_ptr<Option6IA> opt_ia_na_advertise =
1645  boost::static_pointer_cast<Option6IA>(advertise_pkt6->getOption(D6O_IA_NA));
1646  if (!opt_ia_na_advertise) {
1647  isc_throw(Unexpected, "DHCPv6 IA_NA option not found in received "
1648  "packet");
1649  }
1650  size_t addr_offset = getRequestedIpOffset();
1651  boost::shared_ptr<LocalizedOption>
1652  opt_ia_na(new LocalizedOption(opt_ia_na_advertise, addr_offset));
1653  if (!opt_ia_na->valid()) {
1654  isc_throw(BadValue, "Option IA_NA in advertise packet is invalid");
1655  }
1656  pkt6->addOption(opt_ia_na);
1657  // Set server id.
1658  OptionPtr opt_serverid_advertise = advertise_pkt6->getOption(D6O_SERVERID);
1659  if (!opt_serverid_advertise) {
1660  isc_throw(Unexpected, "DHCPV6 SERVERID option not found in received "
1661  "packet");
1662  }
1663  size_t srvid_offset = getServerIdOffset();
1664  boost::shared_ptr<LocalizedOption>
1665  opt_serverid(new LocalizedOption(Option::V6, D6O_SERVERID,
1666  opt_serverid_advertise->getData(),
1667  srvid_offset));
1668  pkt6->addOption(opt_serverid);
1669  // Get randomization offset.
1670  size_t rand_offset = getRandomOffset(arg_idx);
1671  OptionPtr opt_clientid_advertise = advertise_pkt6->getOption(D6O_CLIENTID);
1672  if (!opt_clientid_advertise) {
1673  isc_throw(Unexpected, "DHCPV6 CLIENTID option not found in received packet");
1674  }
1675  rand_offset -= (opt_clientid_advertise->len() - 1);
1676  // Set client id.
1677  boost::shared_ptr<LocalizedOption>
1678  opt_clientid(new LocalizedOption(Option::V6, D6O_CLIENTID,
1679  opt_clientid_advertise->getData(),
1680  rand_offset));
1681  pkt6->addOption(opt_clientid);
1682  // Set default packet data.
1683  setDefaults6(pkt6);
1684 
1685  // Add any extra options that user may have specified.
1686  addExtraOpts(pkt6);
1687 
1688  // Prepare on wire data.
1689  pkt6->rawPack();
1690  // Send packet.
1691  socket_.send(pkt6);
1692  // Update packet stats.
1694 
1695  // When 'T' diagnostics flag is specified it means that user requested
1696  // printing packet contents. It will be just one (first) packet which
1697  // contents will be printed. Here we check if this packet has been already
1698  // collected. If it hasn't we save this packet so as we can print its
1699  // contents when test is finished.
1700  if (options_.testDiags('T') &&
1703  }
1704 }
1705 
1706 void
1707 TestControl::sendSolicit6(const bool preload /*= false*/) {
1708  // Generate DUID to be passed to the packet
1709  uint8_t randomized = 0;
1710  std::vector<uint8_t> duid = generateDuid(randomized);
1711  // Generate transaction id to be set for the new exchange.
1712  const uint32_t transid = generateTransid();
1713  Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, transid));
1714  if (!pkt6) {
1715  isc_throw(Unexpected, "failed to create SOLICIT packet");
1716  }
1717 
1718  // Check if we need to simulate HA failures by pretending no responses were received.
1719  // The DHCPv6 protocol signals that by increasing the elapsed option field. Note it is in 1/100 of a second.
1721  stats_mgr_.getTestPeriod().length().total_seconds() >= options_.getWaitForElapsedTime() &&
1722  stats_mgr_.getTestPeriod().length().total_seconds() < options_.getWaitForElapsedTime() +
1724 
1725 
1726  // Keep increasing elapsed time. The value should start increasing steadily.
1727  uint32_t val = (stats_mgr_.getTestPeriod().length().total_seconds() - options_.getWaitForElapsedTime() + 1)*100;
1728  if (val > 65535) {
1729  val = 65535;
1730  }
1731  OptionPtr elapsed(new OptionInt<uint16_t>(Option::V6, D6O_ELAPSED_TIME, val));
1732  pkt6->addOption(elapsed);
1733  } else {
1734  pkt6->addOption(Option::factory(Option::V6, D6O_ELAPSED_TIME));
1735  }
1736 
1737  if (options_.isRapidCommit()) {
1738  pkt6->addOption(Option::factory(Option::V6, D6O_RAPID_COMMIT));
1739  }
1740  pkt6->addOption(Option::factory(Option::V6, D6O_CLIENTID, duid));
1741  pkt6->addOption(Option::factory(Option::V6, D6O_ORO));
1742 
1743 
1744  // Depending on the lease-type option specified, we should request
1745  // IPv6 address (with IA_NA) or IPv6 prefix (IA_PD) or both.
1746 
1747  // IA_NA
1749  pkt6->addOption(Option::factory(Option::V6, D6O_IA_NA));
1750  }
1751  // IA_PD
1753  pkt6->addOption(Option::factory(Option::V6, D6O_IA_PD));
1754  }
1755 
1756  setDefaults6(pkt6);
1757 
1758  // Add any extra options that user may have specified.
1759  addExtraOpts(pkt6);
1760 
1761  pkt6->pack();
1762  socket_.send(pkt6);
1763  if (!preload) {
1765  }
1766 
1767  saveFirstPacket(pkt6);
1768 }
1769 
1770 void
1771 TestControl::sendSolicit6(const std::vector<uint8_t>& template_buf,
1772  const bool preload /*= false*/) {
1773  const int arg_idx = 0;
1774  // Get transaction id offset.
1775  size_t transid_offset = getTransactionIdOffset(arg_idx);
1776  // Generate transaction id to be set for the new exchange.
1777  const uint32_t transid = generateTransid();
1778  // Create packet.
1779  PerfPkt6Ptr pkt6(new PerfPkt6(&template_buf[0], template_buf.size(),
1780  transid_offset, transid));
1781  if (!pkt6) {
1782  isc_throw(Unexpected, "failed to create SOLICIT packet");
1783  }
1784  size_t rand_offset = getRandomOffset(arg_idx);
1785  // randomized will pick number of bytes randomized so we can
1786  // just use part of the generated duid and substitute a few bytes
1788  uint8_t randomized = 0;
1789  std::vector<uint8_t> duid = generateDuid(randomized);
1790  if (rand_offset > template_buf.size()) {
1791  isc_throw(OutOfRange, "randomization offset is out of bounds");
1792  }
1793  // Store random part of the DUID into the packet.
1794  pkt6->writeAt(rand_offset - randomized + 1,
1795  duid.end() - randomized, duid.end());
1796 
1797  // Prepare on-wire data.
1798  pkt6->rawPack();
1799  setDefaults6(pkt6);
1800 
1801  // Add any extra options that user may have specified.
1802  addExtraOpts(pkt6);
1803 
1804  // Send solicit packet.
1805  socket_.send(pkt6);
1806  if (!preload) {
1807  // Update packet stats.
1809  }
1810  saveFirstPacket(pkt6);
1811 }
1812 
1813 
1814 void
1816  // Interface name.
1817  IfacePtr iface = socket_.getIface();
1818  if (iface == NULL) {
1819  isc_throw(BadValue, "unable to find interface with given index");
1820  }
1821  pkt->setIface(iface->getName());
1822  // Interface index.
1823  pkt->setIndex(socket_.ifindex_);
1824  // Local client's port (68)
1825  pkt->setLocalPort(DHCP4_CLIENT_PORT);
1826  // Server's port (67)
1827  if (options_.getRemotePort()) {
1828  pkt->setRemotePort(options_.getRemotePort());
1829  } else {
1830  pkt->setRemotePort(DHCP4_SERVER_PORT);
1831  }
1832  // The remote server's name or IP.
1833  pkt->setRemoteAddr(IOAddress(options_.getServerName()));
1834  // Set local address.
1835  pkt->setLocalAddr(IOAddress(socket_.addr_));
1836  // Set relay (GIADDR) address to local address if multiple
1837  // subnet mode is not enabled
1838  if (!options_.checkMultiSubnet()) {
1839  pkt->setGiaddr(IOAddress(socket_.addr_));
1840  } else {
1841  pkt->setGiaddr(IOAddress(options_.getRandRelayAddr()));
1842  }
1843  // Pretend that we have one relay (which is us).
1844  pkt->setHops(1);
1845 }
1846 
1847 void
1849  // Interface name.
1850  IfacePtr iface = socket_.getIface();
1851  if (iface == NULL) {
1852  isc_throw(BadValue, "unable to find interface with given index");
1853  }
1854  pkt->setIface(iface->getName());
1855  // Interface index.
1856  pkt->setIndex(socket_.ifindex_);
1857  // Local client's port (547)
1858  pkt->setLocalPort(DHCP6_CLIENT_PORT);
1859  // Server's port (548)
1860  if (options_.getRemotePort()) {
1861  pkt->setRemotePort(options_.getRemotePort());
1862  } else {
1863  pkt->setRemotePort(DHCP6_SERVER_PORT);
1864  }
1865  // Set local address.
1866  pkt->setLocalAddr(socket_.addr_);
1867  // The remote server's name or IP.
1868  pkt->setRemoteAddr(IOAddress(options_.getServerName()));
1869 
1870  // only act as a relay agent when told so.
1873  if (options_.isUseRelayedV6()) {
1874  Pkt6::RelayInfo relay_info;
1875  relay_info.msg_type_ = DHCPV6_RELAY_FORW;
1876  relay_info.hop_count_ = 0;
1877  if (options_.checkMultiSubnet()) {
1878  relay_info.linkaddr_ = IOAddress(options_.getRandRelayAddr());
1879  } else {
1880  relay_info.linkaddr_ = IOAddress(socket_.addr_);
1881  }
1882  relay_info.peeraddr_ = IOAddress(socket_.addr_);
1883  relay_info.options_.insert(options_.getRelayOpts().begin(), options_.getRelayOpts().end());
1884  pkt->addRelayInfo(relay_info);
1885  }
1886 }
1887 
1888 namespace {
1889 
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 
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.
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
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
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
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
A generic exception that is thrown if a function is called in a prohibited way.
A generic exception that is thrown when an object can not be found.
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
A generic exception that is thrown when an unexpected error condition occurs.
isc::asiolink::IOAddress getAddress() const
Returns address contained within this option.
Class that represents IAPREFIX option in DHCPv6.
Forward declaration to OptionInt.
Definition: option_int.h:49
Universe
defines option universe DHCPv4 or DHCPv6
Definition: option.h:83
uint16_t getType() const
Returns option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
Definition: option.h:293
Represents DHCPv4 packet.
Definition: pkt4.h:37
Represents a DHCPv6 packet.
Definition: pkt6.h:44
Socket wrapper structure.
Definition: perf_socket.h:26
virtual dhcp::IfacePtr getIface()=0
See description of this method in PerfSocket class below.
unsigned int ifindex_
Interface index.
Definition: perf_socket.h:29
virtual bool send(const dhcp::Pkt4Ptr &pkt)=0
See description of this method in PerfSocket class below.
bool includes(const Type lease_type) const
Checks if lease type implies request for the address, prefix (or both) as specified by the function a...
bool testDiags(const char diag)
Find if diagnostic flag has been set.
std::string getRandRelayAddr()
Returns random relay address.
int getIncreaseElapsedTime() const
Returns increased elapsed time.
std::string getWrapped() const
Returns wrapped command.
uint8_t getIpVersion() const
Returns IP version.
bool isRapidCommit() const
Check if rapid commit option used.
bool checkMultiSubnet()
Check if multi subnet mode is enabled.
bool isUseFirst() const
Check if server-ID to be taken from first package.
int getRemotePort() const
Returns remote port number.
const isc::dhcp::OptionCollection & getExtraOpts() const
Returns extra options to be inserted.
int getWaitForElapsedTime() const
Returns time to wait for elapsed time increase.
ExchangeMode
2-way (cmd line param -i) or 4-way exchanges
std::vector< std::vector< uint8_t > > MacAddrsVector
A vector holding MAC addresses.
std::string getServerName() const
Returns server name.
const isc::dhcp::OptionCollection & getRelayOpts(uint8_t encapsulation_level=1) const
Returns relay options to be inserted at given level of encapsulation.
LeaseType getLeaseType() const
\ brief Returns the type of lease being requested.
bool isUseRelayedV6() const
Check if generated DHCPv6 messages should appear as relayed.
uint32_t getClientsNum() const
Returns number of simulated clients.
bool isSeeded() const
Checks if seed provided.
uint32_t getSeed() const
Returns random seed.
DHCP option at specific offset.
PerfPkt4 (DHCPv4 packet)
Definition: perf_pkt4.h:42
PerfPkt6 (DHCPv6 packet)
Definition: perf_pkt6.h:41
uint64_t getRcvdPacketsNum(const ExchangeType xchg_type) const
Return total number of received packets.
boost::posix_time::time_period getTestPeriod() const
Get time period since the start of test.
void passSentPacket(const ExchangeType xchg_type, const dhcp::PktPtr &packet)
Adds new packet to the sent packets list.
Sequential numbers generator class.
Definition: test_control.h:158
void saveFirstPacket(const dhcp::Pkt4Ptr &pkt)
Save the first DHCPv4 sent packet of the specified type.
void address6Uniqueness(const dhcp::Pkt6Ptr &pkt6, ExchangeType xchg_type)
Process received v6 addresses uniqueness.
static const uint8_t HW_ETHER_LEN
Length of the Ethernet HW address (MAC) in bytes.
Definition: test_control.h:190
std::map< uint8_t, dhcp::Pkt4Ptr > template_packets_v4_
First packets send.
bool sendMessageFromReply(const uint16_t msg_type)
Send DHCPv6 Renew or Release message.
void addExtraOpts(const dhcp::Pkt4Ptr &pkt4)
Inserts extra options specified by user.
void printDiagnostics() const
Print main diagnostics data.
static void handleChild(int sig)
Handle child signal.
int getRequestedIpOffset() const
Return requested ip offset in a packet.
boost::shared_ptr< NumberGenerator > NumberGeneratorPtr
The default generator pointer.
Definition: test_control.h:155
dhcp::Pkt4Ptr createMessageFromAck(const uint16_t msg_type, const dhcp::Pkt4Ptr &ack)
Creates DHCPREQUEST from a DHCPACK message.
std::vector< uint8_t > generateMacAddress(uint8_t &randomized)
Generate MAC address.
void setDefaults4(const dhcp::Pkt4Ptr &pkt)
Set default DHCPv4 packet parameters.
CommandOptions & options_
Command options.
void setMacAddrGenerator(const NumberGeneratorPtr &generator)
Set new MAC address generator.
Definition: test_control.h:206
void setDefaults6(const dhcp::Pkt6Ptr &pkt)
Set default DHCPv6 packet parameters.
void sendRequest6(const dhcp::Pkt6Ptr &advertise_pkt6)
Send DHCPv6 REQUEST message.
void sendSolicit6(const bool preload=false)
Send DHCPv6 SOLICIT message.
std::map< uint8_t, dhcp::Pkt6Ptr > template_packets_v6_
Template for v6.
PacketStorage< dhcp::Pkt6 > reply_storage_
Storage for reply messages.
void registerOptionFactories() const
Register option factory functions for DHCPv4 or DHCPv6.
std::vector< uint8_t > TemplateBuffer
Packet template buffer.
Definition: test_control.h:124
bool sendMessageFromAck(const uint16_t msg_type)
Send DHCPv4 renew (DHCPREQUEST).
void runWrapped(bool do_stop=false) const
Run wrapped command.
BasePerfSocket & socket_
Socket used for DHCP traffic.
void reset()
Resets internal state of the object.
void address4Uniqueness(const dhcp::Pkt4Ptr &pkt4, ExchangeType xchg_type)
Process received v4 addresses uniqueness.
int getRandomOffset(const int arg_idx) const
Return randomization offset in a packet.
dhcp::Pkt6Ptr createMessageFromReply(const uint16_t msg_type, const dhcp::Pkt6Ptr &reply)
Creates DHCPv6 message from the Reply packet.
int getElapsedTimeOffset() const
Return elapsed time offset in a packet.
void sendRequest4(const dhcp::Pkt4Ptr &discover_pkt4, const dhcp::Pkt4Ptr &offer_pkt4)
Send DHCPv4 REQUEST message.
StatsMgr stats_mgr_
Statistics Manager.
PacketStorage< dhcp::Pkt4 > ack_storage_
Storage for DHCPACK messages.
void copyIaOptions(const dhcp::Pkt6Ptr &pkt_from, dhcp::Pkt6Ptr &pkt_to)
Copies IA_NA or IA_PD option from one packet to another.
void setTransidGenerator(const NumberGeneratorPtr &generator)
Set new transaction id generator.
Definition: test_control.h:195
static void handleInterrupt(int sig)
Handle interrupt signal.
int getTransactionIdOffset(const int arg_idx) const
Return transaction id offset in a packet.
void initPacketTemplates()
Reads packet templates from files.
int getServerIdOffset() const
Return server id offset in a packet.
std::vector< uint8_t > generateDuid(uint8_t &randomized)
Generate DUID.
void sendDiscover4(const bool preload=false)
Send DHCPv4 DISCOVER message.
dhcp::OptionPtr generateClientId(const dhcp::HWAddrPtr &hwaddr) const
Generate DHCPv4 client identifier from HW address.
dhcp::OptionBuffer first_packet_serverid_
Buffer holding server id received in first packet.
uint32_t generateTransid()
generate transaction id.
Definition: test_control.h:538
The OutputBuffer class is a buffer abstraction for manipulating mutable data.
Definition: buffer.h:294
const void * getData() const
Return a pointer to the head of the data stored in the buffer.
Definition: buffer.h:401
size_t getLength() const
Return the length of data written in the buffer.
Definition: buffer.h:403
@ D6O_SERVERID
Definition: dhcp6.h:22
@ D6O_CLIENTID
Definition: dhcp6.h:21
@ D6O_NAME_SERVERS
Definition: dhcp6.h:43
@ D6O_RAPID_COMMIT
Definition: dhcp6.h:34
@ D6O_IA_NA
Definition: dhcp6.h:23
@ D6O_ORO
Definition: dhcp6.h:26
@ D6O_IA_PD
Definition: dhcp6.h:45
@ D6O_DOMAIN_SEARCH
Definition: dhcp6.h:44
@ D6O_IAADDR
Definition: dhcp6.h:25
@ D6O_ELAPSED_TIME
Definition: dhcp6.h:28
@ D6O_IAPREFIX
Definition: dhcp6.h:46
@ DHCPV6_ADVERTISE
Definition: dhcp6.h:199
@ DHCPV6_REQUEST
Definition: dhcp6.h:200
@ DHCPV6_RENEW
Definition: dhcp6.h:202
@ DHCPV6_REPLY
Definition: dhcp6.h:204
@ DHCPV6_SOLICIT
Definition: dhcp6.h:198
@ DHCPV6_RELEASE
Definition: dhcp6.h:205
@ DHCPV6_RELAY_FORW
Definition: dhcp6.h:209
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
ElementPtr copy(ConstElementPtr from, int level)
Copy the data up to a nesting level.
Definition: data.cc:1395
boost::shared_ptr< Element > ElementPtr
Definition: data.h:26
boost::shared_ptr< isc::dhcp::Pkt > PktPtr
A pointer to either Pkt4 or Pkt6 packet.
Definition: pkt.h:859
@ DHO_SUBNET_MASK
Definition: dhcp4.h:70
@ DHO_ROUTERS
Definition: dhcp4.h:72
@ DHO_DOMAIN_NAME
Definition: dhcp4.h:84
@ DHO_DOMAIN_NAME_SERVERS
Definition: dhcp4.h:75
@ DHO_DHCP_MESSAGE_TYPE
Definition: dhcp4.h:122
@ DHO_DHCP_SERVER_IDENTIFIER
Definition: dhcp4.h:123
@ DHO_HOST_NAME
Definition: dhcp4.h:81
@ DHO_DHCP_CLIENT_IDENTIFIER
Definition: dhcp4.h:130
@ DHO_DHCP_REQUESTED_ADDRESS
Definition: dhcp4.h:119
@ DHO_TIME_OFFSET
Definition: dhcp4.h:71
@ DHO_DHCP_PARAMETER_REQUEST_LIST
Definition: dhcp4.h:124
@ DHO_BROADCAST_ADDRESS
Definition: dhcp4.h:97
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition: pkt4.h:547
boost::shared_ptr< Iface > IfacePtr
Type definition for the pointer to an Iface object.
Definition: iface_mgr.h:487
std::multimap< unsigned int, OptionPtr > OptionCollection
A collection of DHCP (v4 or v6) options.
Definition: option.h:40
boost::shared_ptr< Option6IAPrefix > Option6IAPrefixPtr
Pointer to the Option6IAPrefix object.
boost::shared_ptr< HWAddr > HWAddrPtr
Shared pointer to a hardware address structure.
Definition: hwaddr.h:154
boost::shared_ptr< Option6IAAddr > Option6IAAddrPtr
A pointer to the isc::dhcp::Option6IAAddr object.
@ DHCPREQUEST
Definition: dhcp4.h:238
@ DHCPOFFER
Definition: dhcp4.h:237
@ DHCPRELEASE
Definition: dhcp4.h:242
@ DHCPDISCOVER
Definition: dhcp4.h:236
@ DHCPACK
Definition: dhcp4.h:240
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition: pkt6.h:28
std::vector< uint8_t > OptionBuffer
buffer types used in DHCP code.
Definition: option.h:24
@ HTYPE_ETHER
Ethernet 10Mbps.
Definition: dhcp4.h:56
boost::shared_ptr< Option > OptionPtr
Definition: option.h:36
boost::shared_ptr< PerfPkt4 > PerfPkt4Ptr
Definition: perf_pkt4.h:128
ExchangeType
DHCP packet exchange types.
@ RA
DHCPv4 REQUEST-ACK.
@ SA
DHCPv6 SOLICIT-ADVERTISE.
@ RNA
DHCPv4 REQUEST-ACK (renewal)
@ RL
DHCPv6 RELEASE-REPLY.
@ RN
DHCPv6 RENEW-REPLY.
@ DO
DHCPv4 DISCOVER-OFFER.
@ RR
DHCPv6 REQUEST-REPLY.
boost::shared_ptr< PerfPkt6 > PerfPkt6Ptr
Definition: perf_pkt6.h:127
Defines the logger used by the top-level component of kea-lfc.
structure that describes a single relay information
Definition: pkt6.h:85
isc::dhcp::OptionCollection options_
options received from a specified relay, except relay-msg option
Definition: pkt6.h:104
uint8_t msg_type_
message type (RELAY-FORW oro RELAY-REPL)
Definition: pkt6.h:94
isc::asiolink::IOAddress linkaddr_
fixed field in relay-forw/relay-reply
Definition: pkt6.h:96
uint8_t hop_count_
number of traversed relays (up to 32)
Definition: pkt6.h:95
isc::asiolink::IOAddress peeraddr_
fixed field in relay-forw/relay-reply
Definition: pkt6.h:97
isc::asiolink::IOAddress addr_
Definition: socket_info.h:21