Kea  1.5.0
ha_impl.cc
Go to the documentation of this file.
1 // Copyright (C) 2018 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 }
48 
49 void
51  Pkt4Ptr query4;
52  callout_handle.getArgument("query4", query4);
53 
56  try {
57  // We have to unpack the query to get access into HW address which is
58  // used to load balance the packet.
59  query4->unpack();
60 
61  } catch (const SkipRemainingOptionsError& ex) {
62  // An option failed to unpack but we are to attempt to process it
63  // anyway. Log it and let's hope for the best.
65  HA_BUFFER4_RECEIVE_PACKET_OPTIONS_SKIPPED)
66  .arg(ex.what());
67 
68  } catch (const std::exception& ex) {
69  // Packet parsing failed. Drop the packet.
70  LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_BUFFER4_RECEIVE_UNPACK_FAILED)
71  .arg(query4->getRemoteAddr().toText())
72  .arg(query4->getLocalAddr().toText())
73  .arg(query4->getIface())
74  .arg(ex.what());
75 
76  // Increase the statistics of parse failures and dropped packets.
77  isc::stats::StatsMgr::instance().addValue("pkt4-parse-failed",
78  static_cast<int64_t>(1));
79  isc::stats::StatsMgr::instance().addValue("pkt4-receive-drop",
80  static_cast<int64_t>(1));
81 
82 
83  callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
84  return;
85  }
86 
87  // Check if we should process this query. If not, drop it.
88  if (!service_->inScope(query4)) {
89  LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_BUFFER4_RECEIVE_NOT_FOR_US)
90  .arg(query4->getLabel());
91  callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
92 
93  } else {
94  // We have successfully parsed the query so we have to signal
95  // to the server that it must not parse it.
96  callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
97  }
98 }
99 
100 void
102  // If the hook library is configured to not send lease updates to the
103  // partner, there is nothing to do because this whole callout is
104  // currently about sending lease updates.
105  if (!config_->amSendingLeaseUpdates()) {
106  // No need to log it, because it was already logged when configuration
107  // was applied.
108  return;
109  }
110 
111  Pkt4Ptr query4;
112  Lease4CollectionPtr leases4;
113  Lease4CollectionPtr deleted_leases4;
114 
115  // Get all arguments available for the leases4_committed hook point.
116  // If any of these arguments is not available this is a programmatic
117  // error. An exception will be thrown which will be caught by the
118  // caller and logged.
119  callout_handle.getArgument("query4", query4);
120 
121  callout_handle.getArgument("leases4", leases4);
122  callout_handle.getArgument("deleted_leases4", deleted_leases4);
123 
124  // In some cases we may have no leases, e.g. DHCPNAK.
125  if (leases4->empty() && deleted_leases4->empty()) {
126  LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_LEASES4_COMMITTED_NOTHING_TO_UPDATE)
127  .arg(query4->getLabel());
128  return;
129  }
130 
131  // Get the parking lot for this hook point. We're going to remember this
132  // pointer until we unpark the packet.
133  ParkingLotHandlePtr parking_lot = callout_handle.getParkingLotHandlePtr();
134 
135  // Asynchronously send lease updates. In some cases no updates will be sent,
136  // e.g. when this server is in the partner-down state and there are no backup
137  // servers. In those cases we simply return without parking the DHCP query.
138  // The response will be sent to the client immediately.
139  if (service_->asyncSendLeaseUpdates(query4, leases4, deleted_leases4, parking_lot) == 0) {
140  return;
141  }
142 
143  // This is required step every time we ask the server to park the packet.
144  // The reference counting is required to keep the packet parked until
145  // all callouts call unpark. Then, the packet gets unparked and the
146  // associated callback is triggered. The callback resumes packet processing.
147  parking_lot->reference(query4);
148 
149  // The callout returns this status code to indicate to the server that it
150  // should park the query packet.
151  callout_handle.setStatus(CalloutHandle::NEXT_STEP_PARK);
152 }
153 
154 void
156  Pkt6Ptr query6;
157  callout_handle.getArgument("query6", query6);
158 
161  try {
162  // We have to unpack the query to get access into DUID which is
163  // used to load balance the packet.
164  query6->unpack();
165 
166  } catch (const SkipRemainingOptionsError& ex) {
167  // An option failed to unpack but we are to attempt to process it
168  // anyway. Log it and let's hope for the best.
170  HA_BUFFER6_RECEIVE_PACKET_OPTIONS_SKIPPED)
171  .arg(ex.what());
172 
173  } catch (const std::exception& ex) {
174  // Packet parsing failed. Drop the packet.
175  LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_BUFFER6_RECEIVE_UNPACK_FAILED)
176  .arg(query6->getRemoteAddr().toText())
177  .arg(query6->getLocalAddr().toText())
178  .arg(query6->getIface())
179  .arg(ex.what());
180 
181  // Increase the statistics of parse failures and dropped packets.
182  isc::stats::StatsMgr::instance().addValue("pkt6-parse-failed",
183  static_cast<int64_t>(1));
184  isc::stats::StatsMgr::instance().addValue("pkt6-receive-drop",
185  static_cast<int64_t>(1));
186 
187 
188  callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
189  return;
190  }
191 
192  // Check if we should process this query. If not, drop it.
193  if (!service_->inScope(query6)) {
194  LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_BUFFER6_RECEIVE_NOT_FOR_US)
195  .arg(query6->getLabel());
196  callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
197 
198  } else {
199  // We have successfully parsed the query so we have to signal
200  // to the server that it must not parse it.
201  callout_handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
202  }
203 }
204 
205 void
207  // If the hook library is configured to not send lease updates to the
208  // partner, there is nothing to do because this whole callout is
209  // currently about sending lease updates.
210  if (!config_->amSendingLeaseUpdates()) {
211  // No need to log it, because it was already logged when configuration
212  // was applied.
213  return;
214  }
215 
216  Pkt6Ptr query6;
217  Lease6CollectionPtr leases6;
218  Lease6CollectionPtr deleted_leases6;
219 
220  // Get all arguments available for the leases6_committed hook point.
221  // If any of these arguments is not available this is a programmatic
222  // error. An exception will be thrown which will be caught by the
223  // caller and logged.
224  callout_handle.getArgument("query6", query6);
225 
226  callout_handle.getArgument("leases6", leases6);
227  callout_handle.getArgument("deleted_leases6", deleted_leases6);
228 
229  // In some cases we may have no leases.
230  if (leases6->empty() && deleted_leases6->empty()) {
231  LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_LEASES6_COMMITTED_NOTHING_TO_UPDATE)
232  .arg(query6->getLabel());
233  return;
234  }
235 
236  // Get the parking lot for this hook point. We're going to remember this
237  // pointer until we unpark the packet.
238  ParkingLotHandlePtr parking_lot = callout_handle.getParkingLotHandlePtr();
239 
240  // Asynchronously send lease updates. In some cases no updates will be sent,
241  // e.g. when this server is in the partner-down state and there are no backup
242  // servers. In those cases we simply return without parking the DHCP query.
243  // The response will be sent to the client immediately.
244  if (service_->asyncSendLeaseUpdates(query6, leases6, deleted_leases6, parking_lot) == 0) {
245  return;
246  }
247 
248  // This is required step every time we ask the server to park the packet.
249  // The reference counting is required to keep the packet parked until
250  // all callouts call unpark. Then, the packet gets unparked and the
251  // associated callback is triggered. The callback resumes packet processing.
252  parking_lot->reference(query6);
253 
254  // The callout returns this status code to indicate to the server that it
255  // should park the query packet.
256  callout_handle.setStatus(CalloutHandle::NEXT_STEP_PARK);
257 }
258 
259 void
261  std::string command_name;
262  callout_handle.getArgument("name", command_name);
263  if (command_name == "dhcp-enable") {
264  service_->adjustNetworkState();
265  }
266 }
267 
268 void
270  ConstElementPtr response = service_->processHeartbeat();
271  callout_handle.setArgument("response", response);
272 }
273 
274 void
276  // Command must always be provided.
277  ConstElementPtr command;
278  callout_handle.getArgument("command", command);
279 
280  // Retrieve arguments.
281  ConstElementPtr args;
282  static_cast<void>(parseCommand(args, command));
283 
284  ConstElementPtr server_name;
285  unsigned int max_period_value = 0;
286 
287  try {
288  // Arguments are required for the ha-sync command.
289  if (!args) {
290  isc_throw(BadValue, "arguments not found in the 'ha-sync' command");
291  }
292 
293  // Arguments must be a map.
294  if (args->getType() != Element::map) {
295  isc_throw(BadValue, "arguments in the 'ha-sync' command are not a map");
296  }
297 
298  // server-name is mandatory. Otherwise how can we know the server to
299  // communicate with.
300  server_name = args->get("server-name");
301  if (!server_name) {
302  isc_throw(BadValue, "'server-name' is mandatory for the 'ha-sync' command");
303  }
304 
305  // server-name must obviously be a string.
306  if (server_name->getType() != Element::string) {
307  isc_throw(BadValue, "'server-name' must be a string in the 'ha-sync' command");
308  }
309 
310  // max-period is optional. In fact it is optional for dhcp-disable command too.
311  ConstElementPtr max_period = args->get("max-period");
312  if (max_period) {
313  // If it is specified, it must be a positive integer.
314  if ((max_period->getType() != Element::integer) ||
315  (max_period->intValue() <= 0)) {
316  isc_throw(BadValue, "'max-period' must be a positive integer in the 'ha-sync' command");
317  }
318 
319  max_period_value = static_cast<unsigned int>(max_period->intValue());
320  }
321 
322  } catch (const std::exception& ex) {
323  // There was an error while parsing command arguments. Return an error status
324  // code to notify the user.
325  ConstElementPtr response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
326  callout_handle.setArgument("response", response);
327  return;
328  }
329 
330  // Command parsing was successful, so let's process the command.
331  ConstElementPtr response = service_->processSynchronize(server_name->stringValue(),
332  max_period_value);
333  callout_handle.setArgument("response", response);
334 }
335 
336 void
338  // Command must always be provided.
339  ConstElementPtr command;
340  callout_handle.getArgument("command", command);
341 
342  // Retrieve arguments.
343  ConstElementPtr args;
344  static_cast<void>(parseCommand(args, command));
345 
346  std::vector<std::string> scopes_vector;
347 
348  try {
349  // Arguments must be present.
350  if (!args) {
351  isc_throw(BadValue, "arguments not found in the 'ha-scopes' command");
352  }
353 
354  // Arguments must be a map.
355  if (args->getType() != Element::map) {
356  isc_throw(BadValue, "arguments in the 'ha-scopes' command are not a map");
357  }
358 
359  // scopes argument is mandatory.
360  ConstElementPtr scopes = args->get("scopes");
361  if (!scopes) {
362  isc_throw(BadValue, "'scopes' is mandatory for the 'ha-scopes' command");
363  }
364 
365  // It contains a list of scope names.
366  if (scopes->getType() != Element::list) {
367  isc_throw(BadValue, "'scopes' must be a list in the 'ha-scopes' command");
368  }
369 
370  // Retrieve scope names from this list. The list may be empty to clear the
371  // scopes.
372  for (size_t i = 0; i < scopes->size(); ++i) {
373  ConstElementPtr scope = scopes->get(i);
374  if (!scope || scope->getType() != Element::string) {
375  isc_throw(BadValue, "scope name must be a string in the 'scopes' argument");
376  }
377  scopes_vector.push_back(scope->stringValue());
378  }
379 
380  } catch (const std::exception& ex) {
381  // There was an error while parsing command arguments. Return an error status
382  // code to notify the user.
383  ConstElementPtr response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
384  callout_handle.setArgument("response", response);
385  return;
386  }
387 
388  // Command parsing was successful, so let's process the command.
389  ConstElementPtr response = service_->processScopes(scopes_vector);
390  callout_handle.setArgument("response", response);
391 }
392 
393 void
395  ConstElementPtr response = service_->processContinue();
396  callout_handle.setArgument("response", response);
397 }
398 
399 
400 } // end of namespace isc::ha
401 } // end of namespace isc
isc::ha::HAConfig
Storage for High Availability configuration.
Definition: ha_config.h:29
isc::dhcp::NetworkStatePtr
boost::shared_ptr< NetworkState > NetworkStatePtr
Pointer to the NetworkState object.
Definition: network_state.h:156
ha_log.h
isc::hooks::CalloutHandle::setArgument
void setArgument(const std::string &name, T value)
Set argument.
Definition: callout_handle.h:153
isc::log
Definition: buffer_appender_impl.cc:17
pkt4.h
isc::hooks::CalloutHandle
Per-packet callout handle.
Definition: callout_handle.h:84
isc::config::createAnswer
ConstElementPtr createAnswer(const int status_code, const std::string &text, const ConstElementPtr &arg)
Definition: command_interpreter.cc:33
isc::ha::HAImpl::leases4Committed
void leases4Committed(hooks::CalloutHandle &callout_handle)
Implementation of the "leases4_committed" callout.
Definition: ha_impl.cc:101
isc::config
Definition: command_interpreter.cc:23
pkt6.h
isc::config::CONTROL_RESULT_ERROR
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
Definition: command_interpreter.h:42
isc::data
Definition: cfg_to_element.h:25
isc::ha::HAImpl::service_
HAServicePtr service_
Pointer to the high availability service (state machine).
Definition: ha_impl.h:151
io_service.h
isc::ha::ha_logger
isc::log::Logger ha_logger("ha-hooks")
Definition: ha_log.h:17
isc::ha::HAImpl::commandProcessed
void commandProcessed(hooks::CalloutHandle &callout_handle)
Implementation of the "command_processed" callout.
Definition: ha_impl.cc:260
isc::ha::HAImpl::leases6Committed
void leases6Committed(hooks::CalloutHandle &callout_handle)
Implementation of the "leases6_committed" callout.
Definition: ha_impl.cc:206
isc::hooks::ParkingLotHandlePtr
boost::shared_ptr< ParkingLotHandle > ParkingLotHandlePtr
Pointer to the parking lot handle.
Definition: parking_lots.h:292
isc
Defines the logger used by the top-level component of kea-dhcp-ddns.
Definition: agent_parser.cc:144
isc::Exception::what
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
Definition: exceptions/exceptions.cc:32
isc::dhcp::Pkt4Ptr
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition: pkt4.h:546
isc::hooks::CalloutHandle::getArgument
void getArgument(const std::string &name, T &value) const
Get argument.
Definition: callout_handle.h:170
isc::ha::HAImpl::configure
void configure(const data::ConstElementPtr &input_config)
Parases configuration.
Definition: ha_impl.cc:35
isc_throw
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Definition: exceptions/exceptions.h:192
LOG_DEBUG
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
ha_impl.h
isc::ha::HAImpl::buffer4Receive
void buffer4Receive(hooks::CalloutHandle &callout_handle)
Implementation of the "buffer4_receive" callout.
Definition: ha_impl.cc:50
isc::BadValue
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
Definition: exceptions/exceptions.h:132
isc::dhcp::Lease6CollectionPtr
boost::shared_ptr< Lease6Collection > Lease6CollectionPtr
A shared pointer to the collection of IPv6 leases.
Definition: lease.h:608
command_interpreter.h
ha_config_parser.h
isc::ha::HAImpl::startService
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
isc::dhcp
Definition: ctrl_dhcp4_srv.cc:75
isc::ha::HAImpl::synchronizeHandler
void synchronizeHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-sync command.
Definition: ha_impl.cc:275
isc::dhcp::SkipRemainingOptionsError
Exception thrown during option unpacking This exception is thrown when an error has occurred,...
Definition: option.h:52
isc::ha::HAServerType
HAServerType
Lists possible server types for which HA service is created.
Definition: ha_server_type.h:14
isc::dhcp::Lease4CollectionPtr
boost::shared_ptr< Lease4Collection > Lease4CollectionPtr
A shared pointer to the collection of IPv4 leases.
Definition: lease.h:458
stats_mgr.h
isc::ha::HAImpl::config_
HAConfigPtr config_
Holds parsed configuration.
Definition: ha_impl.h:148
isc::config::parseCommand
std::string parseCommand(ConstElementPtr &arg, ConstElementPtr command)
Definition: command_interpreter.cc:170
isc::log::DBGLVL_TRACE_BASIC
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:65
isc::ha::HAConfigParser::parse
void parse(const HAConfigPtr &config_storage, const data::ConstElementPtr &config)
Parses HA configuration.
Definition: ha_config_parser.cc:49
isc::ha::HAConfigParser
Configuration parser for High Availability.
Definition: ha_config_parser.h:19
data.h
isc::hooks::CalloutHandle::getParkingLotHandlePtr
ParkingLotHandlePtr getParkingLotHandlePtr() const
Returns pointer to the parking lot handle for this hook point.
Definition: callout_handle.cc:78
isc::hooks
Definition: callout_handle.cc:21
isc::hooks::CalloutHandle::setStatus
void setStatus(const CalloutNextStep next)
Sets the next processing step.
Definition: callout_handle.h:237
isc::ha::HAImpl::continueHandler
void continueHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-continue command.
Definition: ha_impl.cc:394
isc::data::ConstElementPtr
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:23
isc::stats::StatsMgr::instance
static StatsMgr & instance()
Statistics Manager accessor method.
Definition: stats_mgr.cc:21
isc::stats::StatsMgr::addValue
void addValue(const std::string &name, const int64_t value)
Records incremental integer observation.
Definition: stats_mgr.cc:46
lease.h
isc::ha::HAImpl::heartbeatHandler
void heartbeatHandler(hooks::CalloutHandle &callout_handle)
Implements handle for the heartbeat command.
Definition: ha_impl.cc:269
isc::dhcp::Pkt6Ptr
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition: pkt6.h:28
isc::ha::HAImpl::scopesHandler
void scopesHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-scopes command.
Definition: ha_impl.cc:337
isc::ha::HAImpl::buffer6Receive
void buffer6Receive(hooks::CalloutHandle &callout_handle)
Implementation of the "buffer6_receive" callout.
Definition: ha_impl.cc:155