1 // Copyright (C) 2014-2020 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 #include <dhcp/dhcp4.h>
9 #include <dhcp/option.h>
10 #include <dhcp/option_int_array.h>
11 #include <dhcp/option_vendor.h>
12 #include <dhcp/tests/iface_mgr_test_config.h>
13 #include <dhcpsrv/lease.h>
14 #include <dhcp4/tests/dhcp4_client.h>
15 #include <util/multi_threading_mgr.h>
16 #include <util/range_utilities.h>
17 #include <boost/pointer_cast.hpp>
18 #include <cstdlib>
19
20 using namespace isc::asiolink;
21 using namespace isc::util;
22
23 namespace isc {
24 namespace dhcp {
25 namespace test {
26
Configuration()27 Dhcp4Client::Configuration::Configuration()
28 : routers_(), dns_servers_(), log_servers_(), quotes_servers_(),
29 serverid_("0.0.0.0"), siaddr_(IOAddress::IPV4_ZERO_ADDRESS()) {
30 reset();
31 }
32
33 void
reset()34 Dhcp4Client::Configuration::reset() {
35 routers_.clear();
36 dns_servers_.clear();
37 log_servers_.clear();
38 quotes_servers_.clear();
39 serverid_ = IOAddress("0.0.0.0");
40 siaddr_ = IOAddress::IPV4_ZERO_ADDRESS();
41 sname_.clear();
42 boot_file_name_.clear();
43 lease_ = Lease4();
44 }
45
Dhcp4Client(const Dhcp4Client::State & state)46 Dhcp4Client::Dhcp4Client(const Dhcp4Client::State& state) :
47 config_(),
48 ciaddr_(),
49 curr_transid_(0),
50 dest_addr_("255.255.255.255"),
51 hwaddr_(generateHWAddr()),
52 clientid_(),
53 iface_name_("eth0"),
54 iface_index_(ETH0_INDEX),
55 relay_addr_("192.0.2.2"),
56 requested_options_(),
57 server_facing_relay_addr_("10.0.0.2"),
58 srv_(boost::shared_ptr<NakedDhcpv4Srv>(new NakedDhcpv4Srv(0))),
59 state_(state),
60 use_relay_(false),
61 circuit_id_() {
62 }
63
Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv,const Dhcp4Client::State & state)64 Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv,
65 const Dhcp4Client::State& state) :
66 config_(),
67 ciaddr_(),
68 curr_transid_(0),
69 dest_addr_("255.255.255.255"),
70 fqdn_(),
71 hwaddr_(generateHWAddr()),
72 clientid_(),
73 iface_name_("eth0"),
74 iface_index_(ETH0_INDEX),
75 relay_addr_("192.0.2.2"),
76 requested_options_(),
77 server_facing_relay_addr_("10.0.0.2"),
78 srv_(srv),
79 state_(state),
80 use_relay_(false),
81 circuit_id_() {
82 }
83
84 void
addRequestedAddress(const IOAddress & addr)85 Dhcp4Client::addRequestedAddress(const IOAddress& addr) {
86 if (context_.query_) {
87 Option4AddrLstPtr opt(new Option4AddrLst(DHO_DHCP_REQUESTED_ADDRESS,
88 addr));
89 context_.query_->addOption(opt);
90 }
91 }
92
93 void
appendClientId()94 Dhcp4Client::appendClientId() {
95 if (!context_.query_) {
96 isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
97 " when adding Client Identifier option");
98 }
99
100 if (clientid_) {
101 OptionPtr opt(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
102 clientid_->getClientId()));
103 context_.query_->addOption(opt);
104 }
105 }
106
107 void
appendServerId()108 Dhcp4Client::appendServerId() {
109 OptionPtr opt(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
110 config_.serverid_));
111 context_.query_->addOption(opt);
112 }
113
114 void
appendName()115 Dhcp4Client::appendName() {
116 if (!context_.query_) {
117 isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
118 " when adding FQDN or Hostname option");
119 }
120
121 if (fqdn_) {
122 context_.query_->addOption(fqdn_);
123
124 } else if (hostname_) {
125 context_.query_->addOption(hostname_);
126 }
127 }
128
129 void
appendPRL()130 Dhcp4Client::appendPRL() {
131 if (!context_.query_) {
132 isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
133 " when adding option codes to the PRL option");
134
135 } else if (!requested_options_.empty()) {
136 // Include Parameter Request List if at least one option code
137 // has been specified to be requested.
138 OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
139 DHO_DHCP_PARAMETER_REQUEST_LIST));
140 for (std::set<uint8_t>::const_iterator opt = requested_options_.begin();
141 opt != requested_options_.end(); ++opt) {
142 prl->addValue(*opt);
143 }
144 context_.query_->addOption(prl);
145 }
146 }
147
148 void
applyConfiguration()149 Dhcp4Client::applyConfiguration() {
150 Pkt4Ptr resp = context_.response_;
151 if (!resp) {
152 return;
153 }
154
155 // Let's keep the old lease in case this is a response to Inform.
156 Lease4 old_lease = config_.lease_;
157 config_.reset();
158
159 // Routers
160 Option4AddrLstPtr opt_routers = boost::dynamic_pointer_cast<
161 Option4AddrLst>(resp->getOption(DHO_ROUTERS));
162 if (opt_routers) {
163 config_.routers_ = opt_routers->getAddresses();
164 }
165 // DNS Servers
166 Option4AddrLstPtr opt_dns_servers = boost::dynamic_pointer_cast<
167 Option4AddrLst>(resp->getOption(DHO_DOMAIN_NAME_SERVERS));
168 if (opt_dns_servers) {
169 config_.dns_servers_ = opt_dns_servers->getAddresses();
170 }
171 // Log Servers
172 Option4AddrLstPtr opt_log_servers = boost::dynamic_pointer_cast<
173 Option4AddrLst>(resp->getOption(DHO_LOG_SERVERS));
174 if (opt_log_servers) {
175 config_.log_servers_ = opt_log_servers->getAddresses();
176 }
177 // Quotes Servers
178 Option4AddrLstPtr opt_quotes_servers = boost::dynamic_pointer_cast<
179 Option4AddrLst>(resp->getOption(DHO_COOKIE_SERVERS));
180 if (opt_quotes_servers) {
181 config_.quotes_servers_ = opt_quotes_servers->getAddresses();
182 }
183 // Vendor Specific options
184 OptionVendorPtr opt_vendor = boost::dynamic_pointer_cast<
185 OptionVendor>(resp->getOption(DHO_VIVSO_SUBOPTIONS));
186 if (opt_vendor) {
187 config_.vendor_suboptions_ = opt_vendor->getOptions();
188 }
189 // siaddr
190 config_.siaddr_ = resp->getSiaddr();
191 // sname
192 OptionBuffer buf = resp->getSname();
193 // sname is a fixed length field holding null terminated string. Use
194 // of c_str() guarantees that only a useful portion (ending with null
195 // character) is assigned.
196 config_.sname_.assign(std::string(buf.begin(), buf.end()).c_str());
197 // (boot)file
198 buf = resp->getFile();
199 config_.boot_file_name_.assign(std::string(buf.begin(), buf.end()).c_str());
200 // Server Identifier
201 OptionCustomPtr opt_serverid = boost::dynamic_pointer_cast<
202 OptionCustom>(resp->getOption(DHO_DHCP_SERVER_IDENTIFIER));
203 if (opt_serverid) {
204 config_.serverid_ = opt_serverid->readAddress();
205 }
206
207 // If the message sent was Inform, we don't want to throw
208 // away the old lease info, just the bits about options.
209 if (context_.query_->getType() == DHCPINFORM) {
210 config_.lease_ = old_lease;
211 } else {
212 /// @todo Set the valid lifetime, t1, t2 etc.
213 config_.lease_ = Lease4(IOAddress(context_.response_->getYiaddr()),
214 context_.response_->getHWAddr(),
215 0, 0, 0, time(NULL), 0, false, false,
216 "");
217 }
218 }
219
220 void
createLease(const IOAddress & addr,const uint32_t valid_lft)221 Dhcp4Client::createLease(const IOAddress& addr, const uint32_t valid_lft) {
222 Lease4 lease(addr, hwaddr_, 0, 0, valid_lft,
223 time(NULL), 0, false, false, "");
224 config_.lease_ = lease;
225 }
226
227 Pkt4Ptr
createMsg(const uint8_t msg_type)228 Dhcp4Client::createMsg(const uint8_t msg_type) {
229 Pkt4Ptr msg(new Pkt4(msg_type, curr_transid_++));
230 msg->setHWAddr(hwaddr_);
231 return (msg);
232 }
233
234 void
appendExtraOptions()235 Dhcp4Client::appendExtraOptions() {
236 // If there are any custom options specified, add them all to the message.
237 if (!extra_options_.empty()) {
238 for (OptionCollection::iterator opt = extra_options_.begin();
239 opt != extra_options_.end(); ++opt) {
240 context_.query_->addOption(opt->second);
241 }
242 }
243 }
244
245 void
appendClasses()246 Dhcp4Client::appendClasses() {
247 for (ClientClasses::const_iterator cclass = classes_.cbegin();
248 cclass != classes_.cend(); ++cclass) {
249 context_.query_->addClass(*cclass);
250 }
251 }
252
253 void
doDiscover(const boost::shared_ptr<IOAddress> & requested_addr)254 Dhcp4Client::doDiscover(const boost::shared_ptr<IOAddress>& requested_addr) {
255 context_.query_ = createMsg(DHCPDISCOVER);
256 // Request options if any.
257 appendPRL();
258 // Include FQDN or Hostname.
259 appendName();
260 // Include Client Identifier
261 appendClientId();
262 if (requested_addr) {
263 addRequestedAddress(*requested_addr);
264 }
265 // Override the default ciaddr if specified by a test.
266 if (!ciaddr_.unspecified()) {
267 context_.query_->setCiaddr(ciaddr_);
268 }
269 appendExtraOptions();
270 appendClasses();
271
272 // Send the message to the server.
273 sendMsg(context_.query_);
274 // Expect response.
275 context_.response_ = receiveOneMsg();
276 }
277
278 void
doDORA(const boost::shared_ptr<IOAddress> & requested_addr)279 Dhcp4Client::doDORA(const boost::shared_ptr<IOAddress>& requested_addr) {
280 doDiscover(requested_addr);
281 if (context_.response_ && (context_.response_->getType() == DHCPOFFER)) {
282 doRequest();
283 }
284 }
285
286 void
doInform(const bool set_ciaddr)287 Dhcp4Client::doInform(const bool set_ciaddr) {
288 context_.query_ = createMsg(DHCPINFORM);
289 // Request options if any.
290 appendPRL();
291 // Any other options to be sent by a client.
292 appendExtraOptions();
293 // Add classes.
294 appendClasses();
295 // The client sending a DHCPINFORM message has an IP address obtained
296 // by some other means, e.g. static configuration. The lease which we
297 // are using here is most likely set by the createLease method.
298 if (set_ciaddr) {
299 context_.query_->setCiaddr(config_.lease_.addr_);
300 }
301 context_.query_->setLocalAddr(config_.lease_.addr_);
302 // Send the message to the server.
303 sendMsg(context_.query_);
304 // Expect response. If there is no response, return.
305 context_.response_ = receiveOneMsg();
306 if (!context_.response_) {
307 return;
308 }
309 // If DHCPACK has been returned by the server, use the returned
310 // configuration.
311 if (context_.response_->getType() == DHCPACK) {
312 applyConfiguration();
313 }
314 }
315
316 void
doRelease()317 Dhcp4Client::doRelease() {
318 // There is no response for Release message.
319 context_.response_.reset();
320
321 if (config_.lease_.addr_.isV4Zero()) {
322 isc_throw(Dhcp4ClientError, "failed to send the release"
323 " message because client doesn't have a lease");
324 }
325 context_.query_ = createMsg(DHCPRELEASE);
326 // Set ciaddr to the address which we want to release.
327 context_.query_->setCiaddr(config_.lease_.addr_);
328 // Include client identifier.
329 appendClientId();
330
331 // Remove configuration.
332 config_.reset();
333
334 // Send the message to the server.
335 sendMsg(context_.query_);
336 }
337
338 void
doDecline()339 Dhcp4Client::doDecline() {
340 if (config_.lease_.addr_.isV4Zero()) {
341 isc_throw(Dhcp4ClientError, "failed to send the decline"
342 " message because client doesn't have a lease");
343 }
344
345 context_.query_ = createMsg(DHCPDECLINE);
346
347 // Set ciaddr to 0.
348 context_.query_->setCiaddr(IOAddress("0.0.0.0"));
349
350 // Include Requested IP Address Option
351 addRequestedAddress(config_.lease_.addr_);
352
353 // Include client identifier.
354 appendClientId();
355
356 // Include server identifier.
357 appendServerId();
358
359 // Remove configuration.
360 config_.reset();
361
362 // Send the message to the server.
363 sendMsg(context_.query_);
364 }
365
366 void
doRequest()367 Dhcp4Client::doRequest() {
368 context_.query_ = createMsg(DHCPREQUEST);
369
370 // Override the default ciaddr if specified by a test.
371 if (!ciaddr_.unspecified()) {
372 context_.query_->setCiaddr(ciaddr_);
373 } else if ((state_ == SELECTING) || (state_ == INIT_REBOOT)) {
374 context_.query_->setCiaddr(IOAddress("0.0.0.0"));
375 } else {
376 context_.query_->setCiaddr(IOAddress(config_.lease_.addr_));
377 }
378
379 // Requested IP address.
380 if (state_ == SELECTING) {
381 if (context_.response_ &&
382 (context_.response_->getType() == DHCPOFFER) &&
383 (context_.response_->getYiaddr() != IOAddress("0.0.0.0"))) {
384 addRequestedAddress(context_.response_->getYiaddr());
385 } else {
386 isc_throw(Dhcp4ClientError, "error sending the DHCPREQUEST because"
387 " the received DHCPOFFER message was invalid");
388 }
389 } else if (state_ == INIT_REBOOT) {
390 addRequestedAddress(config_.lease_.addr_);
391 }
392
393 // Server identifier.
394 if (state_ == SELECTING) {
395 if (context_.response_) {
396 OptionPtr opt_serverid =
397 context_.response_->getOption(DHO_DHCP_SERVER_IDENTIFIER);
398 if (!opt_serverid) {
399 isc_throw(Dhcp4ClientError, "missing server identifier in the"
400 " server's response");
401 }
402 context_.query_->addOption(opt_serverid);
403 }
404 }
405
406 // Request options if any.
407 appendPRL();
408 // Include FQDN or Hostname.
409 appendName();
410 // Include Client Identifier
411 appendClientId();
412 // Any other options to be sent by a client.
413 appendExtraOptions();
414 // Add classes.
415 appendClasses();
416 // Send the message to the server.
417 sendMsg(context_.query_);
418 // Expect response.
419 context_.response_ = receiveOneMsg();
420 // If the server has responded, store the configuration received.
421 if (context_.response_) {
422 applyConfiguration();
423 }
424 }
425
426 void
receiveResponse()427 Dhcp4Client::receiveResponse() {
428 context_.response_ = receiveOneMsg();
429 // If the server has responded, store the configuration received.
430 if (context_.response_) {
431 applyConfiguration();
432 }
433 }
434
435 void
includeClientId(const std::string & clientid)436 Dhcp4Client::includeClientId(const std::string& clientid) {
437 if (clientid.empty()) {
438 clientid_.reset();
439
440 } else {
441 clientid_ = ClientId::fromText(clientid);
442 }
443 }
444
445 void
includeFQDN(const uint8_t flags,const std::string & fqdn_name,Option4ClientFqdn::DomainNameType fqdn_type)446 Dhcp4Client::includeFQDN(const uint8_t flags, const std::string& fqdn_name,
447 Option4ClientFqdn::DomainNameType fqdn_type) {
448 fqdn_.reset(new Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
449 fqdn_name, fqdn_type));
450 }
451
452 void
includeHostname(const std::string & name)453 Dhcp4Client::includeHostname(const std::string& name) {
454 if (name.empty()) {
455 hostname_.reset();
456 } else {
457 hostname_.reset(new OptionString(Option::V4, DHO_HOST_NAME, name));
458 }
459 }
460
461 HWAddrPtr
generateHWAddr(const uint8_t htype) const462 Dhcp4Client::generateHWAddr(const uint8_t htype) const {
463 if (htype != HTYPE_ETHER) {
464 isc_throw(isc::NotImplemented,
465 "The hardware address type " << static_cast<int>(htype)
466 << " is currently not supported");
467 }
468 std::vector<uint8_t> hwaddr(HWAddr::ETHERNET_HWADDR_LEN);
469 // Generate ethernet hardware address by assigning random byte values.
470 isc::util::fillRandom(hwaddr.begin(), hwaddr.end());
471 return (HWAddrPtr(new HWAddr(hwaddr, htype)));
472 }
473
474 void
modifyHWAddr()475 Dhcp4Client::modifyHWAddr() {
476 if (!hwaddr_) {
477 hwaddr_ = generateHWAddr();
478 return;
479 }
480 // Modify the HW address by adding 1 to its last byte.
481 ++hwaddr_->hwaddr_[hwaddr_->hwaddr_.size() - 1];
482 }
483
484 void
requestOption(const uint8_t option)485 Dhcp4Client::requestOption(const uint8_t option) {
486 if (option != 0) {
487 requested_options_.insert(option);
488 }
489 }
490
491 void
requestOptions(const uint8_t option1,const uint8_t option2,const uint8_t option3)492 Dhcp4Client::requestOptions(const uint8_t option1, const uint8_t option2,
493 const uint8_t option3) {
494 requested_options_.clear();
495 requestOption(option1);
496 requestOption(option2);
497 requestOption(option3);
498 }
499
500 Pkt4Ptr
receiveOneMsg()501 Dhcp4Client::receiveOneMsg() {
502 Pkt4Ptr msg = srv_->receiveOneMsg();
503 if (!msg) {
504 return (Pkt4Ptr());
505 }
506
507 // Copy the original message to simulate reception over the wire.
508 msg->pack();
509 Pkt4Ptr msg_copy(new Pkt4(static_cast<const uint8_t*>
510 (msg->getBuffer().getData()),
511 msg->getBuffer().getLength()));
512 msg_copy->setRemoteAddr(msg->getLocalAddr());
513 msg_copy->setLocalAddr(msg->getRemoteAddr());
514 msg_copy->setRemotePort(msg->getLocalPort());
515 msg_copy->setLocalPort(msg->getRemotePort());
516 msg_copy->setIface(msg->getIface());
517 msg_copy->setIndex(msg->getIndex());
518
519 msg_copy->unpack();
520
521 return (msg_copy);
522 }
523
524 void
sendMsg(const Pkt4Ptr & msg)525 Dhcp4Client::sendMsg(const Pkt4Ptr& msg) {
526 srv_->shutdown_ = false;
527 if (use_relay_) {
528 msg->setHops(1);
529 msg->setGiaddr(relay_addr_);
530 msg->setLocalAddr(server_facing_relay_addr_);
531 // Insert RAI
532 OptionPtr rai(new Option(Option::V4, DHO_DHCP_AGENT_OPTIONS));
533 // Insert circuit id, if specified.
534 if (!circuit_id_.empty()) {
535 rai->addOption(OptionPtr(new Option(Option::V4, RAI_OPTION_AGENT_CIRCUIT_ID,
536 OptionBuffer(circuit_id_.begin(),
537 circuit_id_.end()))));
538 }
539 msg->addOption(rai);
540 }
541 // Repack the message to simulate wire-data parsing.
542 msg->pack();
543 Pkt4Ptr msg_copy(new Pkt4(static_cast<const uint8_t*>
544 (msg->getBuffer().getData()),
545 msg->getBuffer().getLength()));
546 msg_copy->setRemoteAddr(msg->getLocalAddr());
547 msg_copy->setLocalAddr(dest_addr_);
548 msg_copy->setIface(iface_name_);
549 msg_copy->setIndex(iface_index_);
550 // Copy classes
551 const ClientClasses& classes = msg->getClasses();
552 for (ClientClasses::const_iterator cclass = classes.cbegin();
553 cclass != classes.cend(); ++cclass) {
554 msg_copy->addClass(*cclass);
555 }
556 srv_->fakeReceive(msg_copy);
557
558 try {
559 // Invoke run_one instead of run, because we want to avoid triggering
560 // IO service.
561 srv_->run_one();
562 } catch (...) {
563 // Suppress errors, as the DHCPv4 server does.
564 }
565
566 // Make sure the server processed all packets in MT.
567 isc::util::MultiThreadingMgr::instance().getThreadPool().wait(3);
568 }
569
570 void
setHWAddress(const std::string & hwaddr_str)571 Dhcp4Client::setHWAddress(const std::string& hwaddr_str) {
572 if (hwaddr_str.empty()) {
573 hwaddr_.reset();
574 } else {
575 hwaddr_.reset(new HWAddr(HWAddr::fromText(hwaddr_str)));
576 }
577 }
578
579 void
addExtraOption(const OptionPtr & opt)580 Dhcp4Client::addExtraOption(const OptionPtr& opt) {
581 extra_options_.insert(std::make_pair(opt->getType(), opt));
582 }
583
584 void
addClass(const ClientClass & client_class)585 Dhcp4Client::addClass(const ClientClass& client_class) {
586 if (!classes_.contains(client_class)) {
587 classes_.insert(client_class);
588 }
589 }
590
591 } // end of namespace isc::dhcp::test
592 } // end of namespace isc::dhcp
593 } // end of namespace isc
594