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