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