Kea 3.3.1
ha_config_parser.cc
Go to the documentation of this file.
1// Copyright (C) 2018-2026 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
9#include <ha_config_parser.h>
10#include <ha_log.h>
11#include <ha_service_states.h>
13#include <util/filesystem.h>
14#include <boost/make_shared.hpp>
15#include <limits>
16#include <set>
17
18using namespace isc::data;
19using namespace isc::http;
20
21namespace {
22
24const SimpleDefaults HA_CONFIG_LB_DEFAULTS = {
25 { "delayed-updates-limit", Element::integer, "100" },
26};
27
29const SimpleDefaults HA_CONFIG_DEFAULTS = {
30 { "delayed-updates-limit", Element::integer, "0" },
31 { "heartbeat-delay", Element::integer, "10000" },
32 { "max-ack-delay", Element::integer, "10000" },
33 { "max-response-delay", Element::integer, "60000" },
34 { "max-unacked-clients", Element::integer, "10" },
35 { "max-rejected-lease-updates", Element::integer, "10" },
36 { "require-client-certs", Element::boolean, "true" },
37 { "restrict-commands", Element::boolean, "true" },
38 { "send-lease-updates", Element::boolean, "true" },
39 { "sync-leases", Element::boolean, "true" },
40 { "sync-timeout", Element::integer, "60000" },
41 { "sync-page-limit", Element::integer, "10000" },
42 { "wait-backup-ack", Element::boolean, "false" }
43};
44
46const SimpleDefaults HA_CONFIG_MT_DEFAULTS = {
47 { "enable-multi-threading", Element::boolean, "true" },
48 { "http-client-threads", Element::integer, "0" },
49 { "http-dedicated-listener", Element::boolean, "true" },
50 { "http-listener-threads", Element::integer, "0" }
51};
52
54const SimpleDefaults HA_CONFIG_PEER_DEFAULTS = {
55 { "auto-failover", Element::boolean, "true" }
56};
57
59const SimpleDefaults HA_CONFIG_STATE_DEFAULTS = {
60 { "pause", Element::string, "never" }
61};
62
63} // end of anonymous namespace
64
65namespace isc {
66namespace ha {
67
70 try {
71 auto config_storage = boost::make_shared<HAConfigMapper>();
72
73 // This may cause different types of exceptions. We catch them here
74 // and throw unified exception type.
75 parseAll(config_storage, config);
76 validateRelationships(config_storage);
77 logConfigStatus(config_storage);
78 return (config_storage);
79
80 } catch (const ConfigError& ex) {
81 throw;
82
83 } catch (const std::exception& ex) {
85 }
86}
87
88void
89HAConfigParser::parseAll(const HAConfigMapperPtr& config_storage,
90 const ConstElementPtr& config) {
91 // Config must be provided.
92 if (!config) {
93 isc_throw(ConfigError, "HA configuration must not be null");
94 }
95
96 // Config must be a list. Each contains one relationship between servers in the
97 // HA configuration. Currently we support only one relationship.
98 if (config->getType() != Element::list) {
99 isc_throw(ConfigError, "HA configuration must be a list");
100 }
101
102 // Get the HA configuration.
103 auto const& config_vec = config->listValue();
104 if (config_vec.empty()) {
105 isc_throw(ConfigError, "a list of HA configurations must not be empty");
106 }
107 for (auto const& cfg : config_vec) {
108 parseOne(config_storage, cfg);
109 }
110}
111
112void
113HAConfigParser::parseOne(const HAConfigMapperPtr& config_storage,
114 const ElementPtr& config) {
115 // Config must be provided.
116 if (!config) {
117 isc_throw(ConfigError, "HA configuration must not be null");
118 }
119
120 // Config must be a map.
121 if (config->getType() != Element::map) {
122 isc_throw(ConfigError, "HA configuration for a relationship must be a map");
123 }
124
125 auto rel_config = HAConfig::create();
126
127 // Get 'mode'. That's the first thing to gather because the defaults we
128 // apply to the configuration depend on the mode.
129 rel_config->setHAMode(getString(config, "mode"));
130
131 // Set load-balancing specific defaults.
132 if (rel_config->getHAMode() == HAConfig::LOAD_BALANCING) {
133 setDefaults(config, HA_CONFIG_LB_DEFAULTS);
134 }
135 // Set general defaults.
136 setDefaults(config, HA_CONFIG_DEFAULTS);
137
138 // It must contain peers section.
139 if (!config->contains("peers")) {
140 isc_throw(ConfigError, "'peers' parameter missing in HA configuration");
141 }
142
143 // Peers configuration must be a list of maps.
144 ConstElementPtr peers = config->get("peers");
145 if (peers->getType() != Element::list) {
146 isc_throw(ConfigError, "'peers' parameter must be a list");
147 }
148
149 // State machine configuration must be a map.
150 ConstElementPtr state_machine = config->get("state-machine");
151 ConstElementPtr states_list;
152 if (state_machine) {
153 if (state_machine->getType() != Element::map) {
154 isc_throw(ConfigError, "'state-machine' parameter must be a map");
155 }
156
157 states_list = state_machine->get("states");
158 if (states_list && (states_list->getType() != Element::list)) {
159 isc_throw(ConfigError, "'states' parameter must be a list");
160 }
161 }
162
163 // We have made major sanity checks, so let's try to gather some values.
164
165 // Get 'this-server-name'.
166 rel_config->setThisServerName(getString(config, "this-server-name"));
167
168 // Get 'send-lease-updates'.
169 rel_config->setSendLeaseUpdates(getBoolean(config, "send-lease-updates"));
170
171 // Get 'sync-leases'.
172 rel_config->setSyncLeases(getBoolean(config, "sync-leases"));
173
174 // Get 'sync-timeout'.
175 uint32_t sync_timeout = getAndValidateInteger<uint32_t>(config, "sync-timeout");
176 rel_config->setSyncTimeout(sync_timeout);
177
178 // Get 'sync-page-limit'.
179 uint32_t sync_page_limit = getAndValidateInteger<uint32_t>(config, "sync-page-limit");
180 rel_config->setSyncPageLimit(sync_page_limit);
181
182 // Get 'delayed-updates-limit'.
183 uint32_t delayed_updates_limit = getAndValidateInteger<uint32_t>(config, "delayed-updates-limit");
184 rel_config->setDelayedUpdatesLimit(delayed_updates_limit);
185
186 // Get 'heartbeat-delay'.
187 // Can't use getAndValidateInteger for uint32_t because it is used
188 // as a timer interval which is a long so can have a smaller limit.
189 int64_t heartbeat_delay_max = std::numeric_limits<uint32_t>::max();
190 if (heartbeat_delay_max > std::numeric_limits<long>::max()) {
191 heartbeat_delay_max = std::numeric_limits<long>::max();
192 }
193 int64_t heartbeat_delay64 = getInteger(config, "heartbeat-delay");
194 if (heartbeat_delay64 < 0) {
195 isc_throw(ConfigError, "'heartbeat-delay' must not be negative");
196 }
197 if (heartbeat_delay64 > heartbeat_delay_max) {
198 isc_throw(ConfigError, "'heartbeat-delay' must not be greater than "
199 << heartbeat_delay_max);
200 }
201 uint32_t heartbeat_delay = static_cast<uint32_t>(heartbeat_delay64);
202 rel_config->setHeartbeatDelay(heartbeat_delay);
203
204 // Get 'max-response-delay'.
205 uint32_t max_response_delay = getAndValidateInteger<uint32_t>(config, "max-response-delay");
206 rel_config->setMaxResponseDelay(max_response_delay);
207
208 // Get 'max-ack-delay'.
209 uint32_t max_ack_delay = getAndValidateInteger<uint32_t>(config, "max-ack-delay");
210 rel_config->setMaxAckDelay(max_ack_delay);
211
212 // Get 'max-unacked-clients'.
213 uint32_t max_unacked_clients = getAndValidateInteger<uint32_t>(config, "max-unacked-clients");
214 rel_config->setMaxUnackedClients(max_unacked_clients);
215
216 // Get 'max-rejected-lease-updates'.
217 uint32_t max_rejected_lease_updates = getAndValidateInteger<uint32_t>(config, "max-rejected-lease-updates");
218 rel_config->setMaxRejectedLeaseUpdates(max_rejected_lease_updates);
219
220 // Get 'wait-backup-ack'.
221 rel_config->setWaitBackupAck(getBoolean(config, "wait-backup-ack"));
222
223 // Get multi-threading map.
224 ElementPtr mt_config = boost::const_pointer_cast<Element>(config->get("multi-threading"));
225 if (!mt_config) {
226 // Not there, make an empty one.
227 mt_config = Element::createMap();
228 config->set("multi-threading", mt_config);
229 } else if (mt_config->getType() != Element::map) {
230 isc_throw(ConfigError, "multi-threading configuration must be a map");
231 }
232
233 // Backfill the MT defaults.
234 setDefaults(mt_config, HA_CONFIG_MT_DEFAULTS);
235
236 // Get 'enable-multi-threading'.
237 rel_config->setEnableMultiThreading(getBoolean(mt_config, "enable-multi-threading"));
238
239 // Get 'http-dedicated-listener'.
240 rel_config->setHttpDedicatedListener(getBoolean(mt_config, "http-dedicated-listener"));
241
242 // Get 'http-listener-threads'.
243 uint32_t threads = getAndValidateInteger<uint32_t>(mt_config, "http-listener-threads");
244 rel_config->setHttpListenerThreads(threads);
245
246 // Get 'http-client-threads'.
247 threads = getAndValidateInteger<uint32_t>(mt_config, "http-client-threads");
248 rel_config->setHttpClientThreads(threads);
249
250 // Get optional 'trust-anchor'.
251 ConstElementPtr ca = config->get("trust-anchor");
252 if (ca) {
253 rel_config->setTrustAnchor(getString(config, "trust-anchor"));
254 }
255
256 // Get optional 'cert-file'.
257 ConstElementPtr cert = config->get("cert-file");
258 if (cert) {
259 rel_config->setCertFile(getString(config, "cert-file"));
260 }
261
262 // Get optional 'key-file'.
263 ConstElementPtr key = config->get("key-file");
264 if (key) {
265 rel_config->setKeyFile(getString(config, "key-file"));
266 }
267
268 // Get 'require-client-certs'.
269 rel_config->setRequireClientCerts(getBoolean(config, "require-client-certs"));
270
271 // Get 'restrict-commands'.
272 rel_config->setRestrictCommands(getBoolean(config, "restrict-commands"));
273
274 // Peers configuration parsing.
275 auto const& peers_vec = peers->listValue();
276
277 // Go over configuration of each peer.
278 for (auto const& p : peers_vec) {
279
280 // Peer configuration is held in a map.
281 if (p->getType() != Element::map) {
282 isc_throw(ConfigError, "peer configuration must be a map");
283 }
284
285 setDefaults(p, HA_CONFIG_PEER_DEFAULTS);
286
287 // Server name.
288 auto cfg = rel_config->selectNextPeerConfig(getString(p, "name"));
289
290 // URL.
291 cfg->setUrl(Url(getString(p, "url")));
292
293 // Optional trust anchor.
294 if (p->contains("trust-anchor")) {
295 cfg->setTrustAnchor(getString(p, ("trust-anchor")));
296 }
297
298 // Optional certificate file.
299 if (p->contains("cert-file")) {
300 cfg->setCertFile(getString(p, ("cert-file")));
301 }
302
303 // Optional private key file.
304 if (p->contains("key-file")) {
305 cfg->setKeyFile(getString(p, ("key-file")));
306 }
307
308 // Role.
309 cfg->setRole(getString(p, "role"));
310
311 // Auto failover configuration.
312 cfg->setAutoFailover(getBoolean(p, "auto-failover"));
313
314 // Basic HTTP authentication password.
315 std::string password;
316 if (p->contains("basic-auth-password")) {
317 if (p->contains("basic-auth-password-file")) {
318 isc_throw(dhcp::DhcpConfigError, "only one of "
319 << "basic-auth-password and "
320 << "basic-auth-password-file parameter can be "
321 << "configured in peer '"
322 << cfg->getName() << "'");
323 }
324 password = getString(p, "basic-auth-password");
325 }
326 std::string password_file;
327 if (p->contains("basic-auth-password-file")) {
328 password_file = getString(p, "basic-auth-password-file");
329 try {
330 password = util::file::getContent(password_file);
331 } catch (const std::exception& ex) {
332 isc_throw(dhcp::DhcpConfigError, "bad password file in peer '"
333 << cfg->getName() << "': " << ex.what());
334 }
335 }
336
337 // Basic HTTP authentication user.
338 std::string user;
339 bool do_auth = false;
340 if (p->contains("basic-auth-user")) {
341 if (p->contains("basic-auth-user-file")) {
342 isc_throw(dhcp::DhcpConfigError, "only one of "
343 << "basic-auth-user and "
344 << "basic-auth-user-file parameter can be "
345 << "configured in peer '"
346 << cfg->getName() << "'");
347 }
348 user = getString(p, "basic-auth-user");
349 do_auth = true;
350 }
351 std::string user_file;
352 if (p->contains("basic-auth-user-file")) {
353 user_file = getString(p, "basic-auth-user-file");
354 try {
355 user = util::file::getContent(user_file);
356 do_auth = true;
357 } catch (const std::exception& ex) {
358 isc_throw(dhcp::DhcpConfigError, "bad user file in peer '"
359 << cfg->getName() << "': " << ex.what());
360 }
361 }
362 if (do_auth) {
363 BasicHttpAuthPtr& auth = cfg->getBasicAuth();
364 BasicHttpAuthConfigPtr& auth_config = cfg->getBasicAuthConfig();
365 try {
366 if (!user.empty()) {
367 // Validate the user id value.
368 auth.reset(new BasicHttpAuth(user, password));
369 auth_config.reset(new BasicHttpAuthConfig);
370 auth_config->add(user, user_file, password, password_file);
371 }
372 } catch (const std::exception& ex) {
373 isc_throw(dhcp::DhcpConfigError, ex.what() << " in peer '"
374 << cfg->getName() << "'");
375 }
376 }
377 }
378
379 // Per state configuration is optional.
380 if (states_list) {
381 auto const& states_vec = states_list->listValue();
382
383 std::set<int> configured_states;
384
385 // Go over per state configurations.
386 for (auto const& s : states_vec) {
387
388 // State configuration is held in map.
389 if (s->getType() != Element::map) {
390 isc_throw(ConfigError, "state configuration must be a map");
391 }
392
393 setDefaults(s, HA_CONFIG_STATE_DEFAULTS);
394
395 // Get state name and set per state configuration.
396 std::string state_name = getString(s, "state");
397
398 int state = stringToState(state_name);
399 // Check that this configuration doesn't duplicate existing configuration.
400 if (configured_states.count(state) > 0) {
401 isc_throw(ConfigError, "duplicated configuration for the '"
402 << state_name << "' state");
403 }
404 configured_states.insert(state);
405
406 rel_config->getStateMachineConfig()->
407 getStateConfig(state)->setPausing(getString(s, "pause"));
408 }
409 }
410
411 // We have gone over the entire configuration and stored it in the configuration
412 // storage. However, we need to still validate it to detect errors like:
413 // duplicate secondary/primary servers, no configuration for this server etc.
414 rel_config->validate();
415
416 auto peer_configs = rel_config->getAllServersConfig();
417 for (auto const& peer_config : peer_configs) {
418 try {
419 config_storage->map(peer_config.first, rel_config);
420
421 } catch (const std::exception& ex) {
422 isc_throw(HAConfigValidationError, "server names must be unique for different relationships: "
423 << ex.what());
424 }
425 }
426}
427
428template<typename T>
429T HAConfigParser::getAndValidateInteger(const ConstElementPtr& config,
430 const std::string& parameter_name) {
431 int64_t value = getInteger(config, parameter_name);
432 if (value < 0) {
433 isc_throw(ConfigError, "'" << parameter_name << "' must not be negative");
434
435 } else if (value > std::numeric_limits<T>::max()) {
436 isc_throw(ConfigError, "'" << parameter_name << "' must not be greater than "
437 << +std::numeric_limits<T>::max());
438 }
439
440 return (static_cast<T>(value));
441}
442
443void
444HAConfigParser::logConfigStatus(const HAConfigMapperPtr& config_storage) {
446
447 for (auto const& config : config_storage->getAll()) {
448 // If lease updates are disabled, we want to make sure that the user
449 // realizes that and that he has configured some other mechanism to
450 // populate leases.
451 if (!config->amSendingLeaseUpdates()) {
453 .arg(config->getThisServerName());
454 }
455
456 // Same as above but for lease database synchronization.
457 if (!config->amSyncingLeases()) {
459 .arg(config->getThisServerName());
460 }
461
462 // Unusual configuration.
463 if (config->amSendingLeaseUpdates() !=
464 config->amSyncingLeases()) {
466 .arg(config->getThisServerName())
467 .arg(config->amSendingLeaseUpdates() ? "true" : "false")
468 .arg(config->amSyncingLeases() ? "true" : "false");
469 }
470
471 // With this setting the server will not take ownership of the partner's
472 // scope in case of partner's failure. This setting is OK if the
473 // administrator desires to have more control over scopes selection.
474 // The administrator will need to send ha-scopes command to instruct
475 // the server to take ownership of the scope. In some cases he may
476 // also need to send dhcp-enable command to enable DHCP service
477 // (specifically hot-standby mode for standby server).
478 if (!config->getThisServerConfig()->isAutoFailover()) {
480 .arg(config->getThisServerName());
481 }
482 }
483}
484
485void
486HAConfigParser::validateRelationships(const HAConfigMapperPtr& config_storage) {
487 auto configs = config_storage->getAll();
488 if (configs.size() <= 1) {
489 return;
490 }
491 std::unordered_set<std::string> server_names;
492 for (auto const& config : configs) {
493 // Only the hot-standby mode is supported for multiple relationships.
494 if (config->getHAMode() != HAConfig::HOT_STANDBY) {
495 isc_throw(HAConfigValidationError, "multiple HA relationships are only supported for 'hot-standby' mode");
496 }
497 }
498}
499
500} // namespace ha
501} // namespace isc
@ map
Definition data.h:160
@ integer
Definition data.h:153
@ boolean
Definition data.h:155
@ list
Definition data.h:159
@ string
Definition data.h:157
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition data.cc:355
An exception that is thrown if an error occurs while configuring any server.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
static std::string getString(isc::data::ConstElementPtr scope, const std::string &name)
Returns a string parameter from a scope.
static bool getBoolean(isc::data::ConstElementPtr scope, const std::string &name)
Returns a boolean parameter from a scope.
static int64_t getInteger(isc::data::ConstElementPtr scope, const std::string &name)
Returns an integer parameter from a scope.
static size_t setDefaults(isc::data::ElementPtr scope, const SimpleDefaults &default_values)
Sets the default values.
static HAConfigMapperPtr parse(const data::ConstElementPtr &config)
Parses HA configuration.
static HAConfigPtr create()
Instantiates a HAConfig.
Definition ha_config.cc:179
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition macros.h:20
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition macros.h:26
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:30
std::vector< SimpleDefault > SimpleDefaults
This specifies all default values in a given scope (e.g. a subnet).
boost::shared_ptr< Element > ElementPtr
Definition data.h:29
boost::shared_ptr< HAConfigMapper > HAConfigMapperPtr
Pointer to an object mapping HAConfig to relationships.
Definition ha_config.h:43
isc::log::Logger ha_logger("ha-hooks")
Definition ha_log.h:17
const isc::log::MessageID HA_CONFIGURATION_SUCCESSFUL
Definition ha_messages.h:26
const isc::log::MessageID HA_CONFIG_AUTO_FAILOVER_DISABLED
Definition ha_messages.h:27
const isc::log::MessageID HA_CONFIG_LEASE_UPDATES_AND_SYNCING_DIFFER
Definition ha_messages.h:32
const isc::log::MessageID HA_CONFIG_LEASE_UPDATES_DISABLED
Definition ha_messages.h:33
const isc::log::MessageID HA_CONFIG_LEASE_SYNCING_DISABLED
Definition ha_messages.h:30
int stringToState(const std::string &state_name)
Returns state for a given name.
boost::shared_ptr< BasicHttpAuth > BasicHttpAuthPtr
Type of pointers to basic HTTP authentication objects.
Definition basic_auth.h:70
boost::shared_ptr< BasicHttpAuthConfig > BasicHttpAuthConfigPtr
Type of shared pointers to basic HTTP authentication configuration.
string getContent(string const &file_name)
Get the content of a regular file.
Definition filesystem.cc:33
Defines the logger used by the top-level component of kea-lfc.