Kea 2.2.0
ha_impl.cc
Go to the documentation of this file.
1// Copyright (C) 2018-2021 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_impl.h>
11#include <ha_log.h>
12#include <asiolink/io_service.h>
13#include <cc/data.h>
15#include <dhcp/pkt4.h>
16#include <dhcp/pkt6.h>
17#include <dhcpsrv/lease.h>
18#include <stats/stats_mgr.h>
19
20using namespace isc::asiolink;
21using namespace isc::config;
22using namespace isc::data;
23using namespace isc::dhcp;
24using namespace isc::hooks;
25using namespace isc::log;
26
27namespace isc {
28namespace ha {
29
31 : config_(new HAConfig()) {
32}
33
34void
35HAImpl::configure(const ConstElementPtr& input_config) {
36 HAConfigParser parser;
37 parser.parse(config_, input_config);
38}
39
40void
42 const NetworkStatePtr& network_state,
43 const HAServerType& server_type) {
44 // Create the HA service and crank up the state machine.
45 service_ = boost::make_shared<HAService>(io_service, network_state,
46 config_, server_type);
47 // Schedule a start of the services. This ensures we begin after
48 // the dust has settled and Kea MT mode has been firmly established.
49 io_service->post([&]() { service_->startClientAndListener(); } );
50}
51
53 if (service_) {
54 // Shut down the services explicitly, we need finer control
55 // than relying on destruction order.
56 service_->stopClientAndListener();
57 }
58}
59
60void
62 Pkt4Ptr query4;
63 callout_handle.getArgument("query4", query4);
64
67 try {
68 // We have to unpack the query to get access into HW address which is
69 // used to load balance the packet.
70 if (callout_handle.getStatus() != CalloutHandle::NEXT_STEP_SKIP) {
71 query4->unpack();
72 }
73
74 } catch (const SkipRemainingOptionsError& ex) {
75 // An option failed to unpack but we are to attempt to process it
76 // anyway. Log it and let's hope for the best.
79 .arg(ex.what());
80
81 } catch (const std::exception& ex) {
82 // Packet parsing failed. Drop the packet.
84 .arg(query4->getRemoteAddr().toText())
85 .arg(query4->getLocalAddr().toText())
86 .arg(query4->getIface())
87 .arg(ex.what());
88
89 // Increase the statistics of parse failures and dropped packets.
90 isc::stats::StatsMgr::instance().addValue("pkt4-parse-failed",
91 static_cast<int64_t>(1));
92 isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
93 static_cast<int64_t>(1));
94
95
96 callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
97 return;
98 }
99
100 // Check if we should process this query. If not, drop it.
101 if (!service_->inScope(query4)) {
103 .arg(query4->getLabel());
104 callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
105
106 } else {
107 // We have successfully parsed the query so we have to signal
108 // to the server that it must not parse it.
109 callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
110 }
111}
112
113void
115 // If the hook library is configured to not send lease updates to the
116 // partner, there is nothing to do because this whole callout is
117 // currently about sending lease updates.
118 if (!config_->amSendingLeaseUpdates()) {
119 // No need to log it, because it was already logged when configuration
120 // was applied.
121 return;
122 }
123
124 Pkt4Ptr query4;
125 Lease4CollectionPtr leases4;
126 Lease4CollectionPtr deleted_leases4;
127
128 // Get all arguments available for the leases4_committed hook point.
129 // If any of these arguments is not available this is a programmatic
130 // error. An exception will be thrown which will be caught by the
131 // caller and logged.
132 callout_handle.getArgument("query4", query4);
133
134 callout_handle.getArgument("leases4", leases4);
135 callout_handle.getArgument("deleted_leases4", deleted_leases4);
136
137 // In some cases we may have no leases, e.g. DHCPNAK.
138 if (leases4->empty() && deleted_leases4->empty()) {
140 .arg(query4->getLabel());
141 return;
142 }
143
144 // Get the parking lot for this hook point. We're going to remember this
145 // pointer until we unpark the packet.
146 ParkingLotHandlePtr parking_lot = callout_handle.getParkingLotHandlePtr();
147
148 // Create a reference to the parked packet. This signals that we have a
149 // stake in unparking it.
150 parking_lot->reference(query4);
151
152 // Asynchronously send lease updates. In some cases no updates will be sent,
153 // e.g. when this server is in the partner-down state and there are no backup
154 // servers. In those cases we simply return without parking the DHCP query.
155 // The response will be sent to the client immediately.
156 try {
157 if (service_->asyncSendLeaseUpdates(query4, leases4, deleted_leases4, parking_lot) == 0) {
158 // Dereference the parked packet. This releases our stake in it.
159 parking_lot->dereference(query4);
160 return;
161 }
162 } catch (...) {
163 // Make sure we dereference.
164 parking_lot->dereference(query4);
165 throw;
166 }
167
168 // The callout returns this status code to indicate to the server that it
169 // should leave the packet parked. It will be parked until each hook
170 // library with a reference, unparks the packet.
171 callout_handle.setStatus(CalloutHandle::NEXT_STEP_PARK);
172}
173
174void
176 Pkt6Ptr query6;
177 callout_handle.getArgument("query6", query6);
178
181 try {
182 // We have to unpack the query to get access into DUID which is
183 // used to load balance the packet.
184 if (callout_handle.getStatus() != CalloutHandle::NEXT_STEP_SKIP) {
185 query6->unpack();
186 }
187
188 } catch (const SkipRemainingOptionsError& ex) {
189 // An option failed to unpack but we are to attempt to process it
190 // anyway. Log it and let's hope for the best.
193 .arg(ex.what());
194
195 } catch (const std::exception& ex) {
196 // Packet parsing failed. Drop the packet.
198 .arg(query6->getRemoteAddr().toText())
199 .arg(query6->getLocalAddr().toText())
200 .arg(query6->getIface())
201 .arg(ex.what());
202
203 // Increase the statistics of parse failures and dropped packets.
204 isc::stats::StatsMgr::instance().addValue("pkt6-parse-failed",
205 static_cast<int64_t>(1));
206 isc::stats::StatsMgr::instance().addValue("pkt6-receive-drop",
207 static_cast<int64_t>(1));
208
209
210 callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
211 return;
212 }
213
214 // Check if we should process this query. If not, drop it.
215 if (!service_->inScope(query6)) {
217 .arg(query6->getLabel());
218 callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
219
220 } else {
221 // We have successfully parsed the query so we have to signal
222 // to the server that it must not parse it.
223 callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
224 }
225}
226
227void
229 // If the hook library is configured to not send lease updates to the
230 // partner, there is nothing to do because this whole callout is
231 // currently about sending lease updates.
232 if (!config_->amSendingLeaseUpdates()) {
233 // No need to log it, because it was already logged when configuration
234 // was applied.
235 return;
236 }
237
238 Pkt6Ptr query6;
239 Lease6CollectionPtr leases6;
240 Lease6CollectionPtr deleted_leases6;
241
242 // Get all arguments available for the leases6_committed hook point.
243 // If any of these arguments is not available this is a programmatic
244 // error. An exception will be thrown which will be caught by the
245 // caller and logged.
246 callout_handle.getArgument("query6", query6);
247
248 callout_handle.getArgument("leases6", leases6);
249 callout_handle.getArgument("deleted_leases6", deleted_leases6);
250
251 // In some cases we may have no leases.
252 if (leases6->empty() && deleted_leases6->empty()) {
254 .arg(query6->getLabel());
255 return;
256 }
257
258 // Get the parking lot for this hook point. We're going to remember this
259 // pointer until we unpark the packet.
260 ParkingLotHandlePtr parking_lot = callout_handle.getParkingLotHandlePtr();
261
262 // Create a reference to the parked packet. This signals that we have a
263 // stake in unparking it.
264 parking_lot->reference(query6);
265
266 // Asynchronously send lease updates. In some cases no updates will be sent,
267 // e.g. when this server is in the partner-down state and there are no backup
268 // servers. In those cases we simply return without parking the DHCP query.
269 // The response will be sent to the client immediately.
270 try {
271 if (service_->asyncSendLeaseUpdates(query6, leases6, deleted_leases6, parking_lot) == 0) {
272 // Dereference the parked packet. This releases our stake in it.
273 parking_lot->dereference(query6);
274 return;
275 }
276 } catch (...) {
277 // Make sure we dereference.
278 parking_lot->dereference(query6);
279 throw;
280 }
281
282 // The callout returns this status code to indicate to the server that it
283 // should leave the packet parked. It will be unparked until each hook
284 // library with a reference, unparks the packet.
285 callout_handle.setStatus(CalloutHandle::NEXT_STEP_PARK);
286}
287
288void
290 std::string command_name;
291 callout_handle.getArgument("name", command_name);
292 if (command_name == "status-get") {
293 // Get the response.
294 ConstElementPtr response;
295 callout_handle.getArgument("response", response);
296 if (!response || (response->getType() != Element::map)) {
297 return;
298 }
299 // Get the arguments item from the response.
300 ConstElementPtr resp_args = response->get("arguments");
301 if (!resp_args || (resp_args->getType() != Element::map)) {
302 return;
303 }
304 // Add the ha servers info to arguments.
305 ElementPtr mutable_resp_args =
306 boost::const_pointer_cast<Element>(resp_args);
307
311 auto ha_relationships = Element::createList();
312 auto ha_relationship = Element::createMap();
313 ConstElementPtr ha_servers = service_->processStatusGet();
314 ha_relationship->set("ha-servers", ha_servers);
315 ha_relationship->set("ha-mode", Element::create(HAConfig::HAModeToString(config_->getHAMode())));
316 ha_relationships->add(ha_relationship);
317 mutable_resp_args->set("high-availability", ha_relationships);
318 }
319}
320
321void
323 ConstElementPtr response = service_->processHeartbeat();
324 callout_handle.setArgument("response", response);
325}
326
327void
329 // Command must always be provided.
330 ConstElementPtr command;
331 callout_handle.getArgument("command", command);
332
333 // Retrieve arguments.
334 ConstElementPtr args;
335 static_cast<void>(parseCommand(args, command));
336
337 ConstElementPtr server_name;
338 unsigned int max_period_value = 0;
339
340 try {
341 // Arguments are required for the ha-sync command.
342 if (!args) {
343 isc_throw(BadValue, "arguments not found in the 'ha-sync' command");
344 }
345
346 // Arguments must be a map.
347 if (args->getType() != Element::map) {
348 isc_throw(BadValue, "arguments in the 'ha-sync' command are not a map");
349 }
350
351 // server-name is mandatory. Otherwise how can we know the server to
352 // communicate with.
353 server_name = args->get("server-name");
354 if (!server_name) {
355 isc_throw(BadValue, "'server-name' is mandatory for the 'ha-sync' command");
356 }
357
358 // server-name must obviously be a string.
359 if (server_name->getType() != Element::string) {
360 isc_throw(BadValue, "'server-name' must be a string in the 'ha-sync' command");
361 }
362
363 // max-period is optional. In fact it is optional for dhcp-disable command too.
364 ConstElementPtr max_period = args->get("max-period");
365 if (max_period) {
366 // If it is specified, it must be a positive integer.
367 if ((max_period->getType() != Element::integer) ||
368 (max_period->intValue() <= 0)) {
369 isc_throw(BadValue, "'max-period' must be a positive integer in the 'ha-sync' command");
370 }
371
372 max_period_value = static_cast<unsigned int>(max_period->intValue());
373 }
374
375 } catch (const std::exception& ex) {
376 // There was an error while parsing command arguments. Return an error status
377 // code to notify the user.
379 callout_handle.setArgument("response", response);
380 return;
381 }
382
383 // Command parsing was successful, so let's process the command.
384 ConstElementPtr response = service_->processSynchronize(server_name->stringValue(),
385 max_period_value);
386 callout_handle.setArgument("response", response);
387}
388
389void
391 // Command must always be provided.
392 ConstElementPtr command;
393 callout_handle.getArgument("command", command);
394
395 // Retrieve arguments.
396 ConstElementPtr args;
397 static_cast<void>(parseCommand(args, command));
398
399 std::vector<std::string> scopes_vector;
400
401 try {
402 // Arguments must be present.
403 if (!args) {
404 isc_throw(BadValue, "arguments not found in the 'ha-scopes' command");
405 }
406
407 // Arguments must be a map.
408 if (args->getType() != Element::map) {
409 isc_throw(BadValue, "arguments in the 'ha-scopes' command are not a map");
410 }
411
412 // scopes argument is mandatory.
413 ConstElementPtr scopes = args->get("scopes");
414 if (!scopes) {
415 isc_throw(BadValue, "'scopes' is mandatory for the 'ha-scopes' command");
416 }
417
418 // It contains a list of scope names.
419 if (scopes->getType() != Element::list) {
420 isc_throw(BadValue, "'scopes' must be a list in the 'ha-scopes' command");
421 }
422
423 // Retrieve scope names from this list. The list may be empty to clear the
424 // scopes.
425 for (size_t i = 0; i < scopes->size(); ++i) {
426 ConstElementPtr scope = scopes->get(i);
427 if (!scope || scope->getType() != Element::string) {
428 isc_throw(BadValue, "scope name must be a string in the 'scopes' argument");
429 }
430 scopes_vector.push_back(scope->stringValue());
431 }
432
433 } catch (const std::exception& ex) {
434 // There was an error while parsing command arguments. Return an error status
435 // code to notify the user.
437 callout_handle.setArgument("response", response);
438 return;
439 }
440
441 // Command parsing was successful, so let's process the command.
442 ConstElementPtr response = service_->processScopes(scopes_vector);
443 callout_handle.setArgument("response", response);
444}
445
446void
448 ConstElementPtr response = service_->processContinue();
449 callout_handle.setArgument("response", response);
450}
451
452void
454 // Command must always be provided.
455 ConstElementPtr command;
456 callout_handle.getArgument("command", command);
457
458 // Retrieve arguments.
459 ConstElementPtr args;
460 static_cast<void>(parseCommandWithArgs(args, command));
461
462 ConstElementPtr cancel_op = args->get("cancel");
463 if (!cancel_op) {
464 isc_throw(BadValue, "'cancel' is mandatory for the 'ha-maintenance-notify' command");
465 }
466
467 if (cancel_op->getType() != Element::boolean) {
468 isc_throw(BadValue, "'cancel' must be a boolean in the 'ha-maintenance-notify' command");
469 }
470
471 ConstElementPtr response = service_->processMaintenanceNotify(cancel_op->boolValue());
472 callout_handle.setArgument("response", response);
473}
474
475void
477 ConstElementPtr response = service_->processMaintenanceStart();
478 callout_handle.setArgument("response", response);
479}
480
481void
483 ConstElementPtr response = service_->processMaintenanceCancel();
484 callout_handle.setArgument("response", response);
485}
486
487void
489 ConstElementPtr response = service_->processHAReset();
490 callout_handle.setArgument("response", response);
491}
492
493void
495 ConstElementPtr response = service_->processSyncCompleteNotify();
496 callout_handle.setArgument("response", response);
497}
498
499} // end of namespace isc::ha
500} // 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.
Exception thrown during option unpacking This exception is thrown when an error has occurred,...
Definition: option.h:52
Configuration parser for High Availability.
void parse(const HAConfigPtr &config_storage, const data::ConstElementPtr &config)
Parses HA configuration.
Storage for High Availability configuration.
Definition: ha_config.h:33
static std::string HAModeToString(const HAMode &ha_mode)
Returns HA mode name.
Definition: ha_config.cc:225
void scopesHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-scopes command.
Definition: ha_impl.cc:390
void continueHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-continue command.
Definition: ha_impl.cc:447
void syncCompleteNotifyHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-sync-complete-notify command.
Definition: ha_impl.cc:494
HAServicePtr service_
Pointer to the high availability service (state machine).
Definition: ha_impl.h:178
void configure(const data::ConstElementPtr &input_config)
Parses configuration.
Definition: ha_impl.cc:35
~HAImpl()
Destructor.
Definition: ha_impl.cc:52
void maintenanceCancelHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-cancel command.
Definition: ha_impl.cc:482
void haResetHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-reset command.
Definition: ha_impl.cc:488
void maintenanceNotifyHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-notify command.
Definition: ha_impl.cc:453
void synchronizeHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-sync command.
Definition: ha_impl.cc:328
void startService(const asiolink::IOServicePtr &io_service, const dhcp::NetworkStatePtr &network_state, const HAServerType &server_type)
Creates high availability service using current configuration.
Definition: ha_impl.cc:41
void maintenanceStartHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-start command.
Definition: ha_impl.cc:476
void leases4Committed(hooks::CalloutHandle &callout_handle)
Implementation of the "leases4_committed" callout.
Definition: ha_impl.cc:114
void buffer4Receive(hooks::CalloutHandle &callout_handle)
Implementation of the "buffer4_receive" callout.
Definition: ha_impl.cc:61
HAConfigPtr config_
Holds parsed configuration.
Definition: ha_impl.h:175
void buffer6Receive(hooks::CalloutHandle &callout_handle)
Implementation of the "buffer6_receive" callout.
Definition: ha_impl.cc:175
void commandProcessed(hooks::CalloutHandle &callout_handle)
Implementation of the "command_processed" callout.
Definition: ha_impl.cc:289
HAImpl()
Constructor.
Definition: ha_impl.cc:30
void leases6Committed(hooks::CalloutHandle &callout_handle)
Implementation of the "leases6_committed" callout.
Definition: ha_impl.cc:228
void heartbeatHandler(hooks::CalloutHandle &callout_handle)
Implements handle for the heartbeat command.
Definition: ha_impl.cc:322
Per-packet callout handle.
ParkingLotHandlePtr getParkingLotHandlePtr() const
Returns pointer to the parking lot handle for this hook point.
CalloutNextStep getStatus() const
Returns the next processing step.
void setStatus(const CalloutNextStep next)
Sets the next processing step.
void getArgument(const std::string &name, T &value) const
Get argument.
void setArgument(const std::string &name, T value)
Set argument.
static StatsMgr & instance()
Statistics Manager accessor method.
This file contains several functions and constants that are used for handling commands and responses ...
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
void addValue(const std::string &name, const int64_t value)
Records incremental integer observation.
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
std::string parseCommandWithArgs(ConstElementPtr &arg, ConstElementPtr command)
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
std::string parseCommand(ConstElementPtr &arg, ConstElementPtr command)
ConstElementPtr createAnswer(const int status_code, const std::string &text, const ConstElementPtr &arg)
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
boost::shared_ptr< Element > ElementPtr
Definition: data.h:24
boost::shared_ptr< Lease4Collection > Lease4CollectionPtr
A shared pointer to the collection of IPv4 leases.
Definition: lease.h:501
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition: pkt4.h:544
boost::shared_ptr< NetworkState > NetworkStatePtr
Pointer to the NetworkState object.
boost::shared_ptr< Lease6Collection > Lease6CollectionPtr
A shared pointer to the collection of IPv6 leases.
Definition: lease.h:665
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition: pkt6.h:28
const isc::log::MessageID HA_BUFFER4_RECEIVE_UNPACK_FAILED
Definition: ha_messages.h:14
const isc::log::MessageID HA_LEASES6_COMMITTED_NOTHING_TO_UPDATE
Definition: ha_messages.h:55
const isc::log::MessageID HA_BUFFER4_RECEIVE_PACKET_OPTIONS_SKIPPED
Definition: ha_messages.h:13
const isc::log::MessageID HA_BUFFER6_RECEIVE_UNPACK_FAILED
Definition: ha_messages.h:18
isc::log::Logger ha_logger("ha-hooks")
Definition: ha_log.h:17
const isc::log::MessageID HA_BUFFER6_RECEIVE_PACKET_OPTIONS_SKIPPED
Definition: ha_messages.h:17
HAServerType
Lists possible server types for which HA service is created.
const isc::log::MessageID HA_LEASES4_COMMITTED_NOTHING_TO_UPDATE
Definition: ha_messages.h:53
const isc::log::MessageID HA_BUFFER4_RECEIVE_NOT_FOR_US
Definition: ha_messages.h:12
const isc::log::MessageID HA_BUFFER6_RECEIVE_NOT_FOR_US
Definition: ha_messages.h:16
boost::shared_ptr< ParkingLotHandle > ParkingLotHandlePtr
Pointer to the parking lot handle.
Definition: parking_lots.h:381
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:69
Defines the logger used by the top-level component of kea-lfc.