dhcp4_client.cc 16 KB

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