Kea 2.7.6
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 isc {
23namespace dhcp {
24
25namespace {
26
35const std::set<std::string>&
36getSupportedParams4(const bool identifiers_only = false) {
37 // Holds set of host identifiers.
38 static std::set<std::string> identifiers_set;
39 // Holds set of all supported parameters, including identifiers.
40 static std::set<std::string> params_set;
41 // If this is first execution of this function, we need
42 // to initialize the set.
43 if (identifiers_set.empty()) {
44 identifiers_set.insert("hw-address");
45 identifiers_set.insert("duid");
46 identifiers_set.insert("circuit-id");
47 identifiers_set.insert("client-id");
48 identifiers_set.insert("flex-id");
49 }
50 // Copy identifiers and add all other parameters.
51 if (params_set.empty()) {
52 params_set = identifiers_set;
53 params_set.insert("hostname");
54 params_set.insert("ip-address");
55 params_set.insert("option-data");
56 params_set.insert("next-server");
57 params_set.insert("server-hostname");
58 params_set.insert("boot-file-name");
59 params_set.insert("client-classes");
60 params_set.insert("user-context");
61 }
62 return (identifiers_only ? identifiers_set : params_set);
63}
64
73const std::set<std::string>&
74getSupportedParams6(const bool identifiers_only = false) {
75 // Holds set of host identifiers.
76 static std::set<std::string> identifiers_set;
77 // Holds set of all supported parameters, including identifiers.
78 static std::set<std::string> params_set;
79 // If this is first execution of this function, we need
80 // to initialize the set.
81 if (identifiers_set.empty()) {
82 identifiers_set.insert("hw-address");
83 identifiers_set.insert("duid");
84 identifiers_set.insert("flex-id");
85 }
86 // Copy identifiers and add all other parameters.
87 if (params_set.empty()) {
88 params_set = identifiers_set;
89 params_set.insert("hostname");
90 params_set.insert("ip-addresses");
91 params_set.insert("prefixes");
92 params_set.insert("excluded-prefixes");
93 params_set.insert("option-data");
94 params_set.insert("client-classes");
95 params_set.insert("user-context");
96 }
97 return (identifiers_only ? identifiers_set : params_set);
98}
99
107void
108parsePrefix(std::string prefix, IOAddress& addr, uint8_t& len, std::string msg) {
109 // The slash is mandatory for prefixes. If there is no slash,
110 // return an error.
111 size_t len_pos = prefix.find('/');
112 if (len_pos == std::string::npos) {
113 isc_throw(DhcpConfigError, msg << " requires prefix length "
114 << "be specified in '" << prefix << "'");
115 } else if (len_pos >= prefix.length() - 1) {
116 // If there is nothing after the slash, we should also
117 // report an error.
118 isc_throw(DhcpConfigError, "prefix '" << prefix
119 << "' requires length after '/'");
120
121 }
122
123 // Convert the prefix length from the string to the number.
124 // Note, that we don't use the uint8_t type as the lexical cast
125 // would expect a character, e.g. 'a', instead of prefix length,
126 // e.g. '64'.
127 unsigned int prefix_len = len;
128 try {
129 prefix_len = boost::lexical_cast<unsigned int>(prefix.substr(len_pos + 1));
130 } catch (const boost::bad_lexical_cast&) {
131 isc_throw(DhcpConfigError, "prefix length value '"
132 << prefix.substr(len_pos + 1) << "' is invalid");
133 }
134 if ((prefix_len == 0) || (prefix_len > 128)) {
136 "'prefix-len' value must be in range of [1..128]");
137 }
138 len = static_cast<uint8_t>(prefix_len);
139
140 // Remove the slash character and the prefix length from the
141 // parsed value.
142 prefix.erase(len_pos);
143 addr = IOAddress(prefix);
144
145 if (len != 128) {
146 IOAddress first_address = firstAddrInPrefix(addr, len);
147 if (first_address != addr) {
148 isc_throw(BadValue, "Prefix address: " << addr
149 << " exceeds prefix/prefix-len pair: " << first_address
150 << "/" << prefix_len);
151 }
152 }
153}
154
155}
156
159 isc::data::ConstElementPtr reservation_data,
160 bool encapsulate_options) {
161 return (parseInternal(subnet_id, reservation_data, encapsulate_options));
162}
163
166 isc::data::ConstElementPtr reservation_data,
167 bool) {
168 std::string identifier;
169 std::string identifier_name;
170 std::string hostname;
171 ConstElementPtr user_context;
172 HostPtr host;
173
174 try {
175 // Gather those parameters that are common for both IPv4 and IPv6
176 // reservations.
177 for (auto const& element : reservation_data->mapValue()) {
178 // Check if we support this parameter.
179 if (!isSupportedParameter(element.first)) {
180 isc_throw(DhcpConfigError, "unsupported configuration"
181 " parameter '" << element.first << "'");
182 }
183
184 if (isIdentifierParameter(element.first)) {
185 if (!identifier.empty()) {
186 isc_throw(DhcpConfigError, "the '" << element.first
187 << "' and '" << identifier_name
188 << "' are mutually exclusive");
189 }
190 identifier = element.second->stringValue();
191 identifier_name = element.first;
192
193 } else if (element.first == "hostname") {
194 hostname = element.second->stringValue();
195 } else if (element.first == "user-context") {
196 user_context = element.second;
197 }
198 }
199
200 // Host identifier is a must.
201 if (identifier_name.empty()) {
202 // If there is no identifier specified, we have to display an
203 // error message and include the information what identifiers
204 // are supported.
205 std::ostringstream s;
206 for (auto const& param_name : getSupportedParameters(true)) {
207 if (s.tellp() != std::streampos(0)) {
208 s << ", ";
209 }
210 s << param_name;
211 }
212 isc_throw(DhcpConfigError, "one of the supported identifiers must"
213 " be specified for host reservation: "
214 << s.str());
215
216 }
217
218 // Create a host object from the basic parameters we already parsed.
219 host.reset(new Host(identifier, identifier_name, SUBNET_ID_UNUSED,
220 SUBNET_ID_UNUSED, IOAddress("0.0.0.0"), hostname));
221
222 // Add user context
223 if (user_context) {
224 host->setContext(user_context);
225 }
226 } catch (const std::exception& ex) {
227 // Append line number where the error occurred.
228 isc_throw(DhcpConfigError, ex.what() << " ("
229 << reservation_data->getPosition() << ")");
230 }
231
232 return (host);
233}
234
235bool
236HostReservationParser::isIdentifierParameter(const std::string& param_name) const {
237 return (getSupportedParameters(true).count(param_name) > 0);
238}
239
240bool
241HostReservationParser::isSupportedParameter(const std::string& param_name) const {
242 return (getSupportedParameters(false).count(param_name) > 0);
243}
244
247 isc::data::ConstElementPtr reservation_data,
248 bool encapsulate_options) {
249 HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data,
250 encapsulate_options);
251
252 host->setIPv4SubnetID(subnet_id);
253
254 for (auto const& element : reservation_data->mapValue()) {
255 // For 'option-data' element we will use another parser which
256 // already returns errors with position appended, so don't
257 // surround it with try-catch.
258 if (element.first == "option-data") {
259 CfgOptionPtr cfg_option = host->getCfgOption4();
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_INET);
265 parser.parse(cfg_option, element.second, encapsulate_options);
266
267 // Everything else should be surrounded with try-catch to append
268 // position.
269 } else {
270 try {
271 if (element.first == "ip-address") {
272 host->setIPv4Reservation(IOAddress(element.second->
273 stringValue()));
274 } else if (element.first == "next-server") {
275 host->setNextServer(IOAddress(element.second->stringValue()));
276
277 } else if (element.first == "server-hostname") {
278 host->setServerHostname(element.second->stringValue());
279
280 } else if (element.first == "boot-file-name") {
281 host->setBootFileName(element.second->stringValue());
282
283 } else if (element.first == "client-classes") {
284 for (auto const& class_element : element.second->listValue()) {
285 host->addClientClass4(class_element->stringValue());
286 }
287 }
288
289 } catch (const std::exception& ex) {
290 // Append line number where the error occurred.
291 isc_throw(DhcpConfigError, ex.what() << " ("
292 << element.second->getPosition() << ")");
293 }
294 }
295 }
296
297 return (host);
298}
299
300const std::set<std::string>&
301HostReservationParser4::getSupportedParameters(const bool identifiers_only) const {
302 return (getSupportedParams4(identifiers_only));
303}
304
307 isc::data::ConstElementPtr reservation_data,
308 bool encapsulate_options) {
309 HostPtr host = HostReservationParser::parseInternal(subnet_id, reservation_data,
310 encapsulate_options);
311
312 host->setIPv6SubnetID(subnet_id);
313
314 ConstElementPtr option_data = reservation_data->get("option-data");
315 ConstElementPtr ip_addresses = reservation_data->get("ip-addresses");
316 ConstElementPtr prefixes = reservation_data->get("prefixes");
317 ConstElementPtr excluded_prefixes = reservation_data->get("excluded-prefixes");
318 ConstElementPtr client_classes = reservation_data->get("client-classes");
319
320 // Parse option values. Note that the configuration option parser
321 // returns errors with position information appended, so there is no
322 // need to surround it with try-clause (and rethrow with position
323 // appended).
324 if (option_data) {
325 CfgOptionPtr cfg_option = host->getCfgOption6();
326
327 // This parser is converted to SimpleParser already. It
328 // parses the Element structure immediately, there's no need
329 // to go through build/commit phases.
330 OptionDataListParser parser(AF_INET6);
331 parser.parse(cfg_option, option_data, encapsulate_options);
332 }
333
334 if (ip_addresses) {
335 for (size_t idx = 0; idx < ip_addresses->size(); ++idx) {
336 ConstElementPtr element = ip_addresses->get(idx);
337 try {
338 // For the IPv6 address the prefix length is 128 and the
339 // value specified in the list is a reserved address.
340 std::string addr = element->stringValue();
341 // Create a reservation for the address.
342 host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
343 IOAddress(addr), 128));
344 } catch (const std::exception& ex) {
345 // Append line number where the error occurred.
346 isc_throw(DhcpConfigError, ex.what() << " ("
347 << element->getPosition() << ")");
348 }
349 }
350 }
351
352 if (excluded_prefixes) {
353 if (!prefixes) {
354 isc_throw(DhcpConfigError, "'excluded-prefixes' parameter "
355 "requires the 'prefixes' parameter");
356 }
357 if (excluded_prefixes->size() != prefixes->size()) {
358 isc_throw(DhcpConfigError, "'excluded-prefixes' parameter "
359 "does not match the 'prefixes' parameter: "
360 << excluded_prefixes->size() << " != "
361 << prefixes->size());
362 }
363 }
364
365 if (prefixes) {
366 for (size_t idx = 0; idx < prefixes->size(); ++idx) {
367 ConstElementPtr element = prefixes->get(idx);
368 try {
369 std::string prefix = element->stringValue();
370 IOAddress addr("::");
371 uint8_t prefix_len(128);
372 parsePrefix(prefix, addr, prefix_len, "prefix reservation");
373 IPv6Resrv res(IPv6Resrv::TYPE_PD, addr, prefix_len);
374 std::string exclude("");
375 if (excluded_prefixes) {
376 element = excluded_prefixes->get(idx);
377 exclude = element->stringValue();
378 }
379 if (!exclude.empty()) {
380 IOAddress excluded_prefix("::");
381 uint8_t excluded_prefix_len(0);
382 parsePrefix(exclude, excluded_prefix, excluded_prefix_len,
383 "exclude prefix");
384 // setPDExclude calls the Option6PDExclude constructor
385 // which throws on invalid prefix.
386 res.setPDExclude(excluded_prefix, excluded_prefix_len);
387 }
388 host->addReservation(res);
389 } catch (const std::exception& ex) {
390 // Append line number where the error occurred.
391 isc_throw(DhcpConfigError, ex.what() << " ("
392 << element->getPosition() << ")");
393 }
394 }
395 }
396
397 if (client_classes) {
398 try {
399 for (auto const& class_element : client_classes->listValue()) {
400 host->addClientClass6(class_element->stringValue());
401 }
402 } catch (const std::exception& ex) {
403 // Append line number where the error occurred.
404 isc_throw(DhcpConfigError, ex.what() << " ("
405 << client_classes->getPosition() << ")");
406 }
407 }
408
409 return (host);
410}
411
412const std::set<std::string>&
413HostReservationParser6::getSupportedParameters(const bool identifiers_only) const {
414 return (getSupportedParams6(identifiers_only));
415}
416
420
421void
425
426void
428 // Remove existing identifier types.
429 staging_cfg_->clearIdentifierTypes();
430
431 for (auto const& element : ids_list->listValue()) {
432 std::string id_name = element->stringValue();
433 try {
434 if (id_name != "auto") {
435 if (!isSupportedIdentifier(id_name)) {
436 isc_throw(isc::BadValue, "unsupported identifier '"
437 << id_name << "'");
438 }
439 staging_cfg_->addIdentifierType(id_name);
440
441 } else {
442 // 'auto' is mutually exclusive with other values. If there
443 // are any values in the configuration already it means that
444 // some other values have already been specified.
445 if (!staging_cfg_->getIdentifierTypes().empty()) {
446 isc_throw(isc::BadValue, "if 'auto' keyword is used,"
447 " no other values can be specified within '"
448 "host-reservation-identifiers' list");
449 }
450 // Iterate over all identifier types and for those supported
451 // in a given context (DHCPv4 or DHCPv6) add the identifier type
452 // to the configuration.
453 for (unsigned int i = 0;
454 i <= static_cast<unsigned int>(Host::LAST_IDENTIFIER_TYPE);
455 ++i) {
456 std::string supported_id_name =
458 if (isSupportedIdentifier(supported_id_name)) {
459 staging_cfg_->addIdentifierType(supported_id_name);
460 }
461 }
462 }
463
464 } catch (const std::exception& ex) {
465 // Append line number where the error occurred.
466 isc_throw(DhcpConfigError, ex.what() << " ("
467 << element->getPosition() << ")");
468 }
469 }
470
471 // The parsed list must not be empty.
472 if (staging_cfg_->getIdentifierTypes().empty()) {
473 isc_throw(DhcpConfigError, "'host-reservation-identifiers' list must not"
474 " be empty (" << ids_list->getPosition() << ")");
475 }
476
477}
478
483
484bool
485HostReservationIdsParser4::isSupportedIdentifier(const std::string& id_name) const {
486 return (getSupportedParams4(true).count(id_name) > 0);
487}
488
493
494bool
495HostReservationIdsParser6::isSupportedIdentifier(const std::string& id_name) const {
496 return (getSupportedParams6(true).count(id_name) > 0);
497}
498
499} // end of namespace isc::dhcp
500} // end of namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
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:28
SrvConfigPtr getStagingCfg()
Returns a pointer to the staging configuration.
Definition cfgmgr.cc:120
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:327
IdentifierType
Type of the host identifier.
Definition host.h:337
static const IdentifierType LAST_IDENTIFIER_TYPE
Constant pointing to the last identifier of the IdentifierType enumeration.
Definition host.h:347
static std::string getIdentifierName(const IdentifierType &type)
Returns name of the identifier of a specified type.
Definition host.cc:349
IPv6 reservation for a host.
Definition host.h:163
void setPDExclude(const asiolink::IOAddress &excluded_prefix, const uint8_t excluded_prefix_len)
Sets the Prefix Exclude option.
Definition host.cc:120
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:832
boost::shared_ptr< Host > HostPtr
Pointer to the Host object.
Definition host.h:837
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.