host_reservation_parser.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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 <asiolink/io_address.h>
  8. #include <dhcpsrv/cfgmgr.h>
  9. #include <dhcpsrv/parsers/dhcp_parsers.h>
  10. #include <dhcpsrv/parsers/host_reservation_parser.h>
  11. #include <boost/foreach.hpp>
  12. #include <boost/lexical_cast.hpp>
  13. #include <algorithm>
  14. #include <sys/socket.h>
  15. #include <sstream>
  16. #include <string>
  17. using namespace isc::asiolink;
  18. using namespace isc::data;
  19. namespace {
  20. /// @brief Returns set of the supported parameters for DHCPv4.
  21. ///
  22. /// This function returns the set of supported parameters for
  23. /// host reservation in DHCPv4.
  24. ///
  25. /// @param identifiers_only Indicates if the function should only
  26. /// return supported host identifiers (if true) or all supported
  27. /// parameters (if false).
  28. const std::set<std::string>&
  29. getSupportedParams4(const bool identifiers_only = false) {
  30. // Holds set of host identifiers.
  31. static std::set<std::string> identifiers_set;
  32. // Holds set of all supported parameters, including identifiers.
  33. static std::set<std::string> params_set;
  34. // If this is first execution of this function, we need
  35. // to initialize the set.
  36. if (identifiers_set.empty()) {
  37. identifiers_set.insert("hw-address");
  38. identifiers_set.insert("duid");
  39. identifiers_set.insert("circuit-id");
  40. identifiers_set.insert("client-id");
  41. }
  42. // Copy identifiers and add all other parameters.
  43. if (params_set.empty()) {
  44. params_set = identifiers_set;
  45. params_set.insert("hostname");
  46. params_set.insert("ip-address");
  47. params_set.insert("option-data");
  48. params_set.insert("next-server");
  49. params_set.insert("server-hostname");
  50. params_set.insert("boot-file-name");
  51. }
  52. return (identifiers_only ? identifiers_set : params_set);
  53. }
  54. /// @brief Returns set of the supported parameters for DHCPv6.
  55. ///
  56. /// This function returns the set of supported parameters for
  57. /// host reservation in DHCPv6.
  58. ///
  59. /// @param identifiers_only Indicates if the function should only
  60. /// return supported host identifiers (if true) or all supported
  61. /// parameters (if false).
  62. const std::set<std::string>&
  63. getSupportedParams6(const bool identifiers_only = false) {
  64. // Holds set of host identifiers.
  65. static std::set<std::string> identifiers_set;
  66. // Holds set of all supported parameters, including identifiers.
  67. static std::set<std::string> params_set;
  68. // If this is first execution of this function, we need
  69. // to initialize the set.
  70. if (identifiers_set.empty()) {
  71. identifiers_set.insert("hw-address");
  72. identifiers_set.insert("duid");
  73. }
  74. // Copy identifiers and add all other parameters.
  75. if (params_set.empty()) {
  76. params_set = identifiers_set;
  77. params_set.insert("hostname");
  78. params_set.insert("ip-addresses");
  79. params_set.insert("prefixes");
  80. params_set.insert("option-data");
  81. }
  82. return (identifiers_only ? identifiers_set : params_set);
  83. }
  84. }
  85. namespace isc {
  86. namespace dhcp {
  87. HostReservationParser::HostReservationParser(const SubnetID& subnet_id)
  88. : DhcpConfigParser(), subnet_id_(subnet_id) {
  89. }
  90. void
  91. HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
  92. std::string identifier;
  93. std::string identifier_name;
  94. std::string hostname;
  95. // Gather those parameters that are common for both IPv4 and IPv6
  96. // reservations.
  97. BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
  98. try {
  99. // Check if we support this parameter.
  100. if (!isSupportedParameter(element.first)) {
  101. isc_throw(DhcpConfigError, "unsupported configuration"
  102. " parameter '" << element.first << "'");
  103. }
  104. if (isIdentifierParameter(element.first)) {
  105. if (!identifier.empty()) {
  106. isc_throw(DhcpConfigError, "the '" << element.first
  107. << "' and '" << identifier_name
  108. << "' are mutually exclusive");
  109. }
  110. identifier = element.second->stringValue();
  111. identifier_name = element.first;
  112. } else if (element.first == "hostname") {
  113. hostname = element.second->stringValue();
  114. }
  115. } catch (const std::exception& ex) {
  116. // Append line number where the error occurred.
  117. isc_throw(DhcpConfigError, ex.what() << " ("
  118. << element.second->getPosition() << ")");
  119. }
  120. }
  121. try {
  122. // Host identifier is a must.
  123. if (identifier_name.empty()) {
  124. // If there is no identifier specified, we have to display an
  125. // error message and include the information what identifiers
  126. // are supported.
  127. std::ostringstream s;
  128. BOOST_FOREACH(std::string param_name, getSupportedParameters(true)) {
  129. if (s.tellp() != std::streampos(0)) {
  130. s << ", ";
  131. }
  132. s << param_name;
  133. }
  134. isc_throw(DhcpConfigError, "one of the supported identifiers must"
  135. " be specified for host reservation: "
  136. << s.str());
  137. }
  138. // Create a host object from the basic parameters we already parsed.
  139. host_.reset(new Host(identifier, identifier_name, SubnetID(0),
  140. SubnetID(0), IOAddress("0.0.0.0"), hostname));
  141. } catch (const std::exception& ex) {
  142. // Append line number where the error occurred.
  143. isc_throw(DhcpConfigError, ex.what() << " ("
  144. << reservation_data->getPosition() << ")");
  145. }
  146. }
  147. void
  148. HostReservationParser::addHost(isc::data::ConstElementPtr reservation_data) {
  149. try {
  150. CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host_);
  151. } catch (const std::exception& ex) {
  152. // Append line number to the exception string.
  153. isc_throw(DhcpConfigError, ex.what() << " ("
  154. << reservation_data->getPosition() << ")");
  155. }
  156. }
  157. bool
  158. HostReservationParser::isIdentifierParameter(const std::string& param_name) const {
  159. return (getSupportedParameters(true).count(param_name) > 0);
  160. }
  161. bool
  162. HostReservationParser::isSupportedParameter(const std::string& param_name) const {
  163. return (getSupportedParameters(false).count(param_name) > 0);
  164. }
  165. HostReservationParser4::HostReservationParser4(const SubnetID& subnet_id)
  166. : HostReservationParser(subnet_id) {
  167. }
  168. void
  169. HostReservationParser4::build(isc::data::ConstElementPtr reservation_data) {
  170. HostReservationParser::build(reservation_data);
  171. host_->setIPv4SubnetID(subnet_id_);
  172. BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
  173. // For 'option-data' element we will use another parser which
  174. // already returns errors with position appended, so don't
  175. // surround it with try-catch.
  176. if (element.first == "option-data") {
  177. CfgOptionPtr cfg_option = host_->getCfgOption4();
  178. OptionDataListParser parser(element.first, cfg_option, AF_INET);
  179. parser.build(element.second);
  180. // Everything else should be surrounded with try-catch to append
  181. // position.
  182. } else {
  183. try {
  184. if (element.first == "ip-address") {
  185. host_->setIPv4Reservation(IOAddress(element.second->
  186. stringValue()));
  187. } else if (element.first == "next-server") {
  188. host_->setNextServer(IOAddress(element.second->stringValue()));
  189. } else if (element.first == "server-hostname") {
  190. host_->setServerHostname(element.second->stringValue());
  191. } else if (element.first == "boot-file-name") {
  192. host_->setBootFileName(element.second->stringValue());
  193. }
  194. } catch (const std::exception& ex) {
  195. // Append line number where the error occurred.
  196. isc_throw(DhcpConfigError, ex.what() << " ("
  197. << reservation_data->getPosition() << ")");
  198. }
  199. }
  200. }
  201. addHost(reservation_data);
  202. }
  203. const std::set<std::string>&
  204. HostReservationParser4::getSupportedParameters(const bool identifiers_only) const {
  205. return (getSupportedParams4(identifiers_only));
  206. }
  207. HostReservationParser6::HostReservationParser6(const SubnetID& subnet_id)
  208. : HostReservationParser(subnet_id) {
  209. }
  210. void
  211. HostReservationParser6::build(isc::data::ConstElementPtr reservation_data) {
  212. HostReservationParser::build(reservation_data);
  213. host_->setIPv6SubnetID(subnet_id_);
  214. BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
  215. // Parse option values. Note that the configuration option parser
  216. // returns errors with position information appended, so there is no
  217. // need to surround it with try-clause (and rethrow with position
  218. // appended).
  219. if (element.first == "option-data") {
  220. CfgOptionPtr cfg_option = host_->getCfgOption6();
  221. OptionDataListParser parser(element.first, cfg_option, AF_INET6);
  222. parser.build(element.second);
  223. } else if (element.first == "ip-addresses" || element.first == "prefixes") {
  224. BOOST_FOREACH(ConstElementPtr prefix_element,
  225. element.second->listValue()) {
  226. try {
  227. // For the IPv6 address the prefix length is 128 and the
  228. // value specified in the list is a reserved address.
  229. IPv6Resrv::Type resrv_type = IPv6Resrv::TYPE_NA;
  230. std::string prefix = prefix_element->stringValue();
  231. uint8_t prefix_len = 128;
  232. // If we're dealing with prefixes, instead of addresses,
  233. // we will have to extract the prefix length from the value
  234. // specified in the following format: 2001:db8:2000::/64.
  235. if (element.first == "prefixes") {
  236. // The slash is mandatory for prefixes. If there is no
  237. // slash, return an error.
  238. size_t len_pos = prefix.find('/');
  239. if (len_pos == std::string::npos) {
  240. isc_throw(DhcpConfigError, "prefix reservation"
  241. " requires prefix length be specified"
  242. " in '" << prefix << "'");
  243. // If there is nothing after the slash, we should also
  244. // report an error.
  245. } else if (len_pos >= prefix.length() - 1) {
  246. isc_throw(DhcpConfigError, "prefix '" << prefix
  247. << "' requires length after '/'");
  248. }
  249. // Convert the prefix length from the string to the
  250. // number. Note, that we don't use the uint8_t type
  251. // as the lexical cast would expect a chracter, e.g.
  252. // 'a', instead of prefix length, e.g. '64'.
  253. try {
  254. prefix_len = boost::lexical_cast<
  255. unsigned int>(prefix.substr(len_pos + 1));
  256. } catch (const boost::bad_lexical_cast&) {
  257. isc_throw(DhcpConfigError, "prefix length value '"
  258. << prefix.substr(len_pos + 1)
  259. << "' is invalid");
  260. }
  261. // Remove the slash character and the prefix length
  262. // from the parsed value.
  263. prefix.erase(len_pos);
  264. // Finally, set the reservation type.
  265. resrv_type = IPv6Resrv::TYPE_PD;
  266. }
  267. // Create a reservation for an address or prefix.
  268. host_->addReservation(IPv6Resrv(resrv_type,
  269. IOAddress(prefix),
  270. prefix_len));
  271. } catch (const std::exception& ex) {
  272. // Append line number where the error occurred.
  273. isc_throw(DhcpConfigError, ex.what() << " ("
  274. << prefix_element->getPosition() << ")");
  275. }
  276. }
  277. }
  278. }
  279. // This may fail, but the addHost function will handle this on its own.
  280. addHost(reservation_data);
  281. }
  282. const std::set<std::string>&
  283. HostReservationParser6::getSupportedParameters(const bool identifiers_only) const {
  284. return (getSupportedParams6(identifiers_only));
  285. }
  286. HostReservationIdsParser::HostReservationIdsParser()
  287. : staging_cfg_() {
  288. }
  289. void
  290. HostReservationIdsParser::build(isc::data::ConstElementPtr ids_list) {
  291. // Remove existing identifier types.
  292. staging_cfg_->clearIdentifierTypes();
  293. BOOST_FOREACH(ConstElementPtr element, ids_list->listValue()) {
  294. std::string id_name = element->stringValue();
  295. try {
  296. if (id_name != "auto") {
  297. if (!isSupportedIdentifier(id_name)) {
  298. isc_throw(isc::BadValue, "unsupported identifier '"
  299. << id_name << "'");
  300. }
  301. staging_cfg_->addIdentifierType(id_name);
  302. } else {
  303. // 'auto' is mutually exclusive with other values. If there
  304. // are any values in the configuration already it means that
  305. // some other values have already been specified.
  306. if (!staging_cfg_->getIdentifierTypes().empty()) {
  307. isc_throw(isc::BadValue, "if 'auto' keyword is used,"
  308. " no other values can be specified within '"
  309. "host-reservation-identifiers' list");
  310. }
  311. // Iterate over all identifier types and for those supported
  312. // in a given context (DHCPv4 or DHCPv6) add the identifier type
  313. // to the configuration.
  314. for (unsigned int i = 0;
  315. i <= static_cast<unsigned int>(Host::LAST_IDENTIFIER_TYPE);
  316. ++i) {
  317. std::string supported_id_name =
  318. Host::getIdentifierName(static_cast<Host::IdentifierType>(i));
  319. if (isSupportedIdentifier(supported_id_name)) {
  320. staging_cfg_->addIdentifierType(supported_id_name);
  321. }
  322. }
  323. }
  324. } catch (const std::exception& ex) {
  325. // Append line number where the error occurred.
  326. isc_throw(DhcpConfigError, ex.what() << " ("
  327. << element->getPosition() << ")");
  328. }
  329. }
  330. // The parsed list must not be empty.
  331. if (staging_cfg_->getIdentifierTypes().empty()) {
  332. isc_throw(DhcpConfigError, "'host-reservation-identifiers' list must not"
  333. " be empty (" << ids_list->getPosition() << ")");
  334. }
  335. }
  336. HostReservationIdsParser4::HostReservationIdsParser4()
  337. : HostReservationIdsParser() {
  338. staging_cfg_ = CfgMgr::instance().getStagingCfg()->getCfgHostOperations4();
  339. }
  340. bool
  341. HostReservationIdsParser4::isSupportedIdentifier(const std::string& id_name) const {
  342. return (getSupportedParams4(true).count(id_name) > 0);
  343. }
  344. HostReservationIdsParser6::HostReservationIdsParser6()
  345. : HostReservationIdsParser() {
  346. staging_cfg_ = CfgMgr::instance().getStagingCfg()->getCfgHostOperations6();
  347. }
  348. bool
  349. HostReservationIdsParser6::isSupportedIdentifier(const std::string& id_name) const {
  350. return (getSupportedParams6(true).count(id_name) > 0);
  351. }
  352. } // end of namespace isc::dhcp
  353. } // end of namespace isc