dhcp4_client.cc 16 KB

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