Kea  2.3.7
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 void
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
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:507
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition: pkt4.h:547
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:680
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:56
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:54
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.