Kea  1.9.9-git
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>
14 #include <cc/command_interpreter.h>
15 #include <dhcp/pkt4.h>
16 #include <dhcp/pkt6.h>
17 #include <dhcpsrv/lease.h>
18 #include <stats/stats_mgr.h>
19 
20 using namespace isc::asiolink;
21 using namespace isc::config;
22 using namespace isc::data;
23 using namespace isc::dhcp;
24 using namespace isc::hooks;
25 using namespace isc::log;
26 
27 namespace isc {
28 namespace ha {
29 
30 HAImpl::HAImpl()
31  : config_(new HAConfig()) {
32 }
33 
34 void
35 HAImpl::configure(const ConstElementPtr& input_config) {
36  HAConfigParser parser;
37  parser.parse(config_, input_config);
38 }
39 
40 void
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 
60 void
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 
113 void
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 
174 void
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 
227 void
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 
288 void
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 
321 void
323  ConstElementPtr response = service_->processHeartbeat();
324  callout_handle.setArgument("response", response);
325 }
326 
327 void
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.
378  ConstElementPtr response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
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 
389 void
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.
436  ConstElementPtr response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
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 
446 void
448  ConstElementPtr response = service_->processContinue();
449  callout_handle.setArgument("response", response);
450 }
451 
452 void
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 
475 void
477  ConstElementPtr response = service_->processMaintenanceStart();
478  callout_handle.setArgument("response", response);
479 }
480 
481 void
483  ConstElementPtr response = service_->processMaintenanceCancel();
484  callout_handle.setArgument("response", response);
485 }
486 
487 void
489  ConstElementPtr response = service_->processHAReset();
490  callout_handle.setArgument("response", response);
491 }
492 
493 } // end of namespace isc::ha
494 } // end of namespace isc
const isc::log::MessageID HA_BUFFER6_RECEIVE_UNPACK_FAILED
Definition: ha_messages.h:18
const isc::log::MessageID HA_BUFFER6_RECEIVE_PACKET_OPTIONS_SKIPPED
Definition: ha_messages.h:17
void setStatus(const CalloutNextStep next)
Sets the next processing step.
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
~HAImpl()
Destructor.
Definition: ha_impl.cc:52
void parse(const HAConfigPtr &config_storage, const data::ConstElementPtr &config)
Parses HA configuration.
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:65
void synchronizeHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-sync command.
Definition: ha_impl.cc:328
Configuration parser for High Availability.
ConstElementPtr createAnswer(const int status_code, const std::string &text, const ConstElementPtr &arg)
Exception thrown during option unpacking This exception is thrown when an error has occurred...
Definition: option.h:51
void maintenanceCancelHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-cancel command.
Definition: ha_impl.cc:482
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
std::string parseCommandWithArgs(ConstElementPtr &arg, ConstElementPtr command)
boost::shared_ptr< Element > ElementPtr
Definition: data.h:20
void maintenanceNotifyHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-notify command.
Definition: ha_impl.cc:453
HAServicePtr service_
Pointer to the high availability service (state machine).
Definition: ha_impl.h:173
void maintenanceStartHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-start command.
Definition: ha_impl.cc:476
ParkingLotHandlePtr getParkingLotHandlePtr() const
Returns pointer to the parking lot handle for this hook point.
HAServerType
Lists possible server types for which HA service is created.
const isc::log::MessageID HA_BUFFER4_RECEIVE_NOT_FOR_US
Definition: ha_messages.h:12
static StatsMgr & instance()
Statistics Manager accessor method.
void buffer6Receive(hooks::CalloutHandle &callout_handle)
Implementation of the "buffer6_receive" callout.
Definition: ha_impl.cc:175
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
#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...
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition: pkt6.h:28
Per-packet callout handle.
boost::shared_ptr< Lease4Collection > Lease4CollectionPtr
A shared pointer to the collection of IPv4 leases.
Definition: lease.h:490
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition: pkt4.h:544
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:23
void haResetHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-reset command.
Definition: ha_impl.cc:488
void setArgument(const std::string &name, T value)
Set argument.
void scopesHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-scopes command.
Definition: ha_impl.cc:390
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
void continueHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-continue command.
Definition: ha_impl.cc:447
void addValue(const std::string &name, const int64_t value)
Records incremental integer observation.
Defines the logger used by the top-level component of kea-dhcp-ddns.
void configure(const data::ConstElementPtr &input_config)
Parses configuration.
Definition: ha_impl.cc:35
std::string parseCommand(ConstElementPtr &arg, ConstElementPtr command)
void leases4Committed(hooks::CalloutHandle &callout_handle)
Implementation of the "leases4_committed" callout.
Definition: ha_impl.cc:114
const isc::log::MessageID HA_BUFFER4_RECEIVE_UNPACK_FAILED
Definition: ha_messages.h:14
This file contains several functions and constants that are used for handling commands and responses ...
static std::string HAModeToString(const HAMode &ha_mode)
Returns HA mode name.
Definition: ha_config.cc:224
HAConfigPtr config_
Holds parsed configuration.
Definition: ha_impl.h:170
boost::shared_ptr< Lease6Collection > Lease6CollectionPtr
A shared pointer to the collection of IPv6 leases.
Definition: lease.h:644
boost::shared_ptr< NetworkState > NetworkStatePtr
Pointer to the NetworkState object.
The Element class represents a piece of data, used by the command channel and configuration parts...
Definition: data.h:66
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
isc::log::Logger ha_logger("ha-hooks")
Definition: ha_log.h:17
void getArgument(const std::string &name, T &value) const
Get argument.
void buffer4Receive(hooks::CalloutHandle &callout_handle)
Implementation of the "buffer4_receive" callout.
Definition: ha_impl.cc:61
void commandProcessed(hooks::CalloutHandle &callout_handle)
Implementation of the "command_processed" callout.
Definition: ha_impl.cc:289
boost::shared_ptr< ParkingLotHandle > ParkingLotHandlePtr
Pointer to the parking lot handle.
Definition: parking_lots.h:375
const isc::log::MessageID HA_LEASES4_COMMITTED_NOTHING_TO_UPDATE
Definition: ha_messages.h:53
void leases6Committed(hooks::CalloutHandle &callout_handle)
Implementation of the "leases6_committed" callout.
Definition: ha_impl.cc:228
const isc::log::MessageID HA_BUFFER6_RECEIVE_NOT_FOR_US
Definition: ha_messages.h:16
void heartbeatHandler(hooks::CalloutHandle &callout_handle)
Implements handle for the heartbeat command.
Definition: ha_impl.cc:322
CalloutNextStep getStatus() const
Returns the next processing step.
Storage for High Availability configuration.
Definition: ha_config.h:33