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