Kea  2.1.7-git
host_reservation_parser.cc
Go to the documentation of this file.
1 // Copyright (C) 2014-2018 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 <asiolink/io_address.h>
9 #include <dhcpsrv/cfgmgr.h>
12 #include <boost/foreach.hpp>
13 #include <boost/lexical_cast.hpp>
14 #include <algorithm>
15 #include <sys/socket.h>
16 #include <sstream>
17 #include <string>
18 
19 using namespace isc::asiolink;
20 using namespace isc::data;
21 
22 namespace {
23 
32 const std::set<std::string>&
33 getSupportedParams4(const bool identifiers_only = false) {
34  // Holds set of host identifiers.
35  static std::set<std::string> identifiers_set;
36  // Holds set of all supported parameters, including identifiers.
37  static std::set<std::string> params_set;
38  // If this is first execution of this function, we need
39  // to initialize the set.
40  if (identifiers_set.empty()) {
41  identifiers_set.insert("hw-address");
42  identifiers_set.insert("duid");
43  identifiers_set.insert("circuit-id");
44  identifiers_set.insert("client-id");
45  identifiers_set.insert("flex-id");
46  }
47  // Copy identifiers and add all other parameters.
48  if (params_set.empty()) {
49  params_set = identifiers_set;
50  params_set.insert("hostname");
51  params_set.insert("ip-address");
52  params_set.insert("option-data");
53  params_set.insert("next-server");
54  params_set.insert("server-hostname");
55  params_set.insert("boot-file-name");
56  params_set.insert("client-classes");
57  params_set.insert("user-context");
58  }
59  return (identifiers_only ? identifiers_set : params_set);
60 }
61 
70 const std::set<std::string>&
71 getSupportedParams6(const bool identifiers_only = false) {
72  // Holds set of host identifiers.
73  static std::set<std::string> identifiers_set;
74  // Holds set of all supported parameters, including identifiers.
75  static std::set<std::string> params_set;
76  // If this is first execution of this function, we need
77  // to initialize the set.
78  if (identifiers_set.empty()) {
79  identifiers_set.insert("hw-address");
80  identifiers_set.insert("duid");
81  identifiers_set.insert("flex-id");
82  }
83  // Copy identifiers and add all other parameters.
84  if (params_set.empty()) {
85  params_set = identifiers_set;
86  params_set.insert("hostname");
87  params_set.insert("ip-addresses");
88  params_set.insert("prefixes");
89  params_set.insert("option-data");
90  params_set.insert("client-classes");
91  params_set.insert("user-context");
92  }
93  return (identifiers_only ? identifiers_set : params_set);
94 }
95 
96 }
97 
98 namespace isc {
99 namespace dhcp {
100 
101 HostPtr
102 HostReservationParser::parse(const SubnetID& subnet_id,
103  isc::data::ConstElementPtr reservation_data) {
104  return (parseInternal(subnet_id, reservation_data));
105 }
106 
107 HostPtr
108 HostReservationParser::parseInternal(const SubnetID&,
109  isc::data::ConstElementPtr reservation_data) {
110  std::string identifier;
111  std::string identifier_name;
112  std::string hostname;
113  ConstElementPtr user_context;
114  HostPtr host;
115 
116  try {
117  // Gather those parameters that are common for both IPv4 and IPv6
118  // reservations.
119  BOOST_FOREACH(auto element, reservation_data->mapValue()) {
120  // Check if we support this parameter.
121  if (!isSupportedParameter(element.first)) {
122  isc_throw(DhcpConfigError, "unsupported configuration"
123  " parameter '" << element.first << "'");
124  }
125 
126  if (isIdentifierParameter(element.first)) {
127  if (!identifier.empty()) {
128  isc_throw(DhcpConfigError, "the '" << element.first
129  << "' and '" << identifier_name
130  << "' are mutually exclusive");
131  }
132  identifier = element.second->stringValue();
133  identifier_name = element.first;
134 
135  } else if (element.first == "hostname") {
136  hostname = element.second->stringValue();
137  } else if (element.first == "user-context") {
138  user_context = element.second;
139  }
140  }
141 
142  // Host identifier is a must.
143  if (identifier_name.empty()) {
144  // If there is no identifier specified, we have to display an
145  // error message and include the information what identifiers
146  // are supported.
147  std::ostringstream s;
148  BOOST_FOREACH(std::string param_name, getSupportedParameters(true)) {
149  if (s.tellp() != std::streampos(0)) {
150  s << ", ";
151  }
152  s << param_name;
153  }
154  isc_throw(DhcpConfigError, "one of the supported identifiers must"
155  " be specified for host reservation: "
156  << s.str());
157 
158  }
159 
160  // Create a host object from the basic parameters we already parsed.
161  host.reset(new Host(identifier, identifier_name, SUBNET_ID_UNUSED,
162  SUBNET_ID_UNUSED, IOAddress("0.0.0.0"), hostname));
163 
164  // Add user context
165  if (user_context) {
166  host->setContext(user_context);
167  }
168  } catch (const std::exception& ex) {
169  // Append line number where the error occurred.
170  isc_throw(DhcpConfigError, ex.what() << " ("
171  << reservation_data->getPosition() << ")");
172  }
173 
174  return (host);
175 }
176 
177 bool
178 HostReservationParser::isIdentifierParameter(const std::string& param_name) const {
179  return (getSupportedParameters(true).count(param_name) > 0);
180 }
181 
182 bool
183 HostReservationParser::isSupportedParameter(const std::string& param_name) const {
184  return (getSupportedParameters(false).count(param_name) > 0);
185 }
186 
187 HostPtr
188 HostReservationParser4::parseInternal(const SubnetID& subnet_id,
189  isc::data::ConstElementPtr reservation_data) {
190  HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data);
191 
192  host->setIPv4SubnetID(subnet_id);
193 
194  BOOST_FOREACH(auto element, reservation_data->mapValue()) {
195  // For 'option-data' element we will use another parser which
196  // already returns errors with position appended, so don't
197  // surround it with try-catch.
198  if (element.first == "option-data") {
199  CfgOptionPtr cfg_option = host->getCfgOption4();
200 
201  // This parser is converted to SimpleParser already. It
202  // parses the Element structure immediately, there's no need
203  // to go through build/commit phases.
204  OptionDataListParser parser(AF_INET);
205  parser.parse(cfg_option, element.second);
206 
207  // Everything else should be surrounded with try-catch to append
208  // position.
209  } else {
210  try {
211  if (element.first == "ip-address") {
212  host->setIPv4Reservation(IOAddress(element.second->
213  stringValue()));
214  } else if (element.first == "next-server") {
215  host->setNextServer(IOAddress(element.second->stringValue()));
216 
217  } else if (element.first == "server-hostname") {
218  host->setServerHostname(element.second->stringValue());
219 
220  } else if (element.first == "boot-file-name") {
221  host->setBootFileName(element.second->stringValue());
222 
223  } else if (element.first == "client-classes") {
224  BOOST_FOREACH(ConstElementPtr class_element,
225  element.second->listValue()) {
226  host->addClientClass4(class_element->stringValue());
227  }
228  }
229 
230  } catch (const std::exception& ex) {
231  // Append line number where the error occurred.
232  isc_throw(DhcpConfigError, ex.what() << " ("
233  << element.second->getPosition() << ")");
234  }
235  }
236  }
237 
238  return (host);
239 }
240 
241 const std::set<std::string>&
242 HostReservationParser4::getSupportedParameters(const bool identifiers_only) const {
243  return (getSupportedParams4(identifiers_only));
244 }
245 
246 HostPtr
247 HostReservationParser6::parseInternal(const SubnetID& subnet_id,
248  isc::data::ConstElementPtr reservation_data) {
249  HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data);
250 
251  host->setIPv6SubnetID(subnet_id);
252 
253  BOOST_FOREACH(auto element, reservation_data->mapValue()) {
254  // Parse option values. Note that the configuration option parser
255  // returns errors with position information appended, so there is no
256  // need to surround it with try-clause (and rethrow with position
257  // appended).
258  if (element.first == "option-data") {
259  CfgOptionPtr cfg_option = host->getCfgOption6();
260 
261  // This parser is converted to SimpleParser already. It
262  // parses the Element structure immediately, there's no need
263  // to go through build/commit phases.
264  OptionDataListParser parser(AF_INET6);
265  parser.parse(cfg_option, element.second);
266 
267  } else if (element.first == "ip-addresses" || element.first == "prefixes") {
268  BOOST_FOREACH(ConstElementPtr prefix_element,
269  element.second->listValue()) {
270  try {
271  // For the IPv6 address the prefix length is 128 and the
272  // value specified in the list is a reserved address.
273  IPv6Resrv::Type resrv_type = IPv6Resrv::TYPE_NA;
274  std::string prefix = prefix_element->stringValue();
275  uint8_t prefix_len = 128;
276 
277  // If we're dealing with prefixes, instead of addresses,
278  // we will have to extract the prefix length from the value
279  // specified in the following format: 2001:db8:2000::/64.
280  if (element.first == "prefixes") {
281  // The slash is mandatory for prefixes. If there is no
282  // slash, return an error.
283  size_t len_pos = prefix.find('/');
284  if (len_pos == std::string::npos) {
285  isc_throw(DhcpConfigError, "prefix reservation"
286  " requires prefix length be specified"
287  " in '" << prefix << "'");
288 
289  // If there is nothing after the slash, we should also
290  // report an error.
291  } else if (len_pos >= prefix.length() - 1) {
292  isc_throw(DhcpConfigError, "prefix '" << prefix
293  << "' requires length after '/'");
294 
295  }
296 
297  // Convert the prefix length from the string to the
298  // number. Note, that we don't use the uint8_t type
299  // as the lexical cast would expect a character, e.g.
300  // 'a', instead of prefix length, e.g. '64'.
301  try {
302  prefix_len = boost::lexical_cast<
303  unsigned int>(prefix.substr(len_pos + 1));
304 
305  } catch (const boost::bad_lexical_cast&) {
306  isc_throw(DhcpConfigError, "prefix length value '"
307  << prefix.substr(len_pos + 1)
308  << "' is invalid");
309  }
310 
311  // Remove the slash character and the prefix length
312  // from the parsed value.
313  prefix.erase(len_pos);
314 
315  // Finally, set the reservation type.
316  resrv_type = IPv6Resrv::TYPE_PD;
317  }
318 
319  // Create a reservation for an address or prefix.
320  host->addReservation(IPv6Resrv(resrv_type,
321  IOAddress(prefix),
322  prefix_len));
323 
324  } catch (const std::exception& ex) {
325  // Append line number where the error occurred.
326  isc_throw(DhcpConfigError, ex.what() << " ("
327  << prefix_element->getPosition() << ")");
328  }
329  }
330 
331 
332  } else if (element.first == "client-classes") {
333  try {
334  BOOST_FOREACH(ConstElementPtr class_element,
335  element.second->listValue()) {
336  host->addClientClass6(class_element->stringValue());
337  }
338  } catch (const std::exception& ex) {
339  // Append line number where the error occurred.
340  isc_throw(DhcpConfigError, ex.what() << " ("
341  << element.second->getPosition() << ")");
342  }
343  }
344  }
345 
346  return (host);
347 }
348 
349 const std::set<std::string>&
350 HostReservationParser6::getSupportedParameters(const bool identifiers_only) const {
351  return (getSupportedParams6(identifiers_only));
352 }
353 
354 HostReservationIdsParser::HostReservationIdsParser()
355  : staging_cfg_() {
356 }
357 
358 void
360  parseInternal(ids_list);
361 }
362 
363 void
365  // Remove existing identifier types.
366  staging_cfg_->clearIdentifierTypes();
367 
368  BOOST_FOREACH(ConstElementPtr element, ids_list->listValue()) {
369  std::string id_name = element->stringValue();
370  try {
371  if (id_name != "auto") {
372  if (!isSupportedIdentifier(id_name)) {
373  isc_throw(isc::BadValue, "unsupported identifier '"
374  << id_name << "'");
375  }
376  staging_cfg_->addIdentifierType(id_name);
377 
378  } else {
379  // 'auto' is mutually exclusive with other values. If there
380  // are any values in the configuration already it means that
381  // some other values have already been specified.
382  if (!staging_cfg_->getIdentifierTypes().empty()) {
383  isc_throw(isc::BadValue, "if 'auto' keyword is used,"
384  " no other values can be specified within '"
385  "host-reservation-identifiers' list");
386  }
387  // Iterate over all identifier types and for those supported
388  // in a given context (DHCPv4 or DHCPv6) add the identifier type
389  // to the configuration.
390  for (unsigned int i = 0;
391  i <= static_cast<unsigned int>(Host::LAST_IDENTIFIER_TYPE);
392  ++i) {
393  std::string supported_id_name =
394  Host::getIdentifierName(static_cast<Host::IdentifierType>(i));
395  if (isSupportedIdentifier(supported_id_name)) {
396  staging_cfg_->addIdentifierType(supported_id_name);
397  }
398  }
399  }
400 
401  } catch (const std::exception& ex) {
402  // Append line number where the error occurred.
403  isc_throw(DhcpConfigError, ex.what() << " ("
404  << element->getPosition() << ")");
405  }
406  }
407 
408  // The parsed list must not be empty.
409  if (staging_cfg_->getIdentifierTypes().empty()) {
410  isc_throw(DhcpConfigError, "'host-reservation-identifiers' list must not"
411  " be empty (" << ids_list->getPosition() << ")");
412  }
413 
414 }
415 
418  staging_cfg_ = CfgMgr::instance().getStagingCfg()->getCfgHostOperations4();
419 }
420 
421 bool
422 HostReservationIdsParser4::isSupportedIdentifier(const std::string& id_name) const {
423  return (getSupportedParams4(true).count(id_name) > 0);
424 }
425 
428  staging_cfg_ = CfgMgr::instance().getStagingCfg()->getCfgHostOperations6();
429 }
430 
431 bool
432 HostReservationIdsParser6::isSupportedIdentifier(const std::string& id_name) const {
433  return (getSupportedParams6(true).count(id_name) > 0);
434 }
435 
436 } // end of namespace isc::dhcp
437 } // end of namespace isc
void parse(const CfgOptionPtr &cfg, isc::data::ConstElementPtr option_data_list)
Parses a list of options, instantiates them and stores in cfg.
boost::shared_ptr< CfgOption > CfgOptionPtr
Non-const pointer.
Definition: cfg_option.h:706
boost::shared_ptr< Host > HostPtr
Pointer to the Host object.
Definition: host.h:785
static CfgMgr & instance()
returns a single instance of Configuration Manager
Definition: cfgmgr.cc:25
virtual bool isSupportedIdentifier(const std::string &id_name) const
Checks if specified identifier name is supported for DHCPv4.
static std::string getIdentifierName(const IdentifierType &type)
Returns name of the identifier of a specified type.
Definition: host.cc:293
IPv6 reservation for a host.
Definition: host.h:161
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
void parse(isc::data::ConstElementPtr ids_list)
Parses a list of host identifiers.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
To be removed. Please use ConfigError instead.
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
Parser for option data values within a subnet.
Represents a device with IPv4 and/or IPv6 reservations.
Definition: host.h:297
Type
Type of the reservation.
Definition: host.h:167
Parser for a list of host identifiers.
Defines the logger used by the top-level component of kea-lfc.
virtual bool isSupportedIdentifier(const std::string &id_name) const =0
Checks if specified identifier name is supported in the context of the parser.
virtual bool isSupportedIdentifier(const std::string &id_name) const
Checks if specified identifier name is supported for DHCPv6.
CfgHostOperationsPtr staging_cfg_
Pointer to the object holding configuration.
virtual void parseInternal(isc::data::ConstElementPtr ids_list)
Parses a list of host identifiers.
static const IdentifierType LAST_IDENTIFIER_TYPE
Constant pointing to the last identifier of the IdentifierType enumeration.
Definition: host.h:317
uint32_t SubnetID
Defines unique IPv4 or IPv6 subnet identifier.
Definition: subnet_id.h:24
SrvConfigPtr getStagingCfg()
Returns a pointer to the staging configuration.
Definition: cfgmgr.cc:167